├── .github ├── FUNDING.yml ├── ISSUE_TEMPLATE │ ├── bug_report.yml │ └── feature_request.yml ├── dependabot.yml └── workflows │ └── android.yml ├── .gitignore ├── .idea ├── .name ├── appInsightsSettings.xml ├── compiler.xml ├── deploymentTargetDropDown.xml ├── deploymentTargetSelector.xml ├── gradle.xml ├── icon.svg ├── inspectionProfiles │ └── Project_Default.xml ├── kotlinc.xml ├── migrations.xml ├── misc.xml ├── studiobot.xml └── vcs.xml ├── CHANGELOG.md ├── CODE_OF_CONDUCT.md ├── EULA.md ├── LICENSE ├── README.md ├── app ├── .gitignore ├── build.gradle.kts ├── google-services.json ├── proguard-rules.pro └── src │ └── main │ ├── AndroidManifest.xml │ ├── kotlin │ └── com │ │ └── d4rk │ │ └── cleaner │ │ ├── data │ │ ├── core │ │ │ └── AppCoreManager.kt │ │ ├── datastore │ │ │ └── DataStore.kt │ │ └── model │ │ │ └── ui │ │ │ ├── appmanager │ │ │ └── ui │ │ │ │ └── ApkInfo.kt │ │ │ ├── imageoptimizer │ │ │ ├── CompressionLevel.kt │ │ │ └── ImageOptimizerState.kt │ │ │ ├── memorymanager │ │ │ ├── InternalStorageInfo.kt │ │ │ ├── RamInfo.kt │ │ │ └── StorageInfo.kt │ │ │ ├── navigation │ │ │ └── BottomNavigationScreen.kt │ │ │ └── screens │ │ │ ├── MainScreenState.kt │ │ │ ├── UiAppManagerModel.kt │ │ │ ├── UiHelpScreen.kt │ │ │ ├── UiHomeModel.kt │ │ │ ├── UiMainScreen.kt │ │ │ ├── UiMemoryManagerModel.kt │ │ │ └── UiTrashModel.kt │ │ ├── ui │ │ ├── components │ │ │ ├── ads │ │ │ │ └── BannerAds.kt │ │ │ ├── buttons │ │ │ │ └── Buttons.kt │ │ │ ├── dialogs │ │ │ │ ├── ConfirmationAlertDialog.kt │ │ │ │ ├── SelectLanguageAlertDialog.kt │ │ │ │ └── SelectStartupScreenAlertDialog.kt │ │ │ ├── layouts │ │ │ │ ├── CarouselLayout.kt │ │ │ │ └── NonLazyGrid.kt │ │ │ ├── modifiers │ │ │ │ └── ViewPagerModifiers.kt │ │ │ ├── navigation │ │ │ │ ├── BottomNavigationBar.kt │ │ │ │ ├── LeftNavigationRail.kt │ │ │ │ ├── NavigationDrawer.kt │ │ │ │ ├── NavigationHost.kt │ │ │ │ ├── TopAppBar.kt │ │ │ │ └── actions │ │ │ │ │ └── DropdownMenuActions.kt │ │ │ └── progressbars │ │ │ │ └── ProgressBar.kt │ │ ├── screens │ │ │ ├── analyze │ │ │ │ ├── AnalyzeScreen.kt │ │ │ │ └── components │ │ │ │ │ ├── DateHeader.kt │ │ │ │ │ ├── DeleteOrTrashConfirmation.kt │ │ │ │ │ ├── FileCard.kt │ │ │ │ │ ├── FilesByDateSection.kt │ │ │ │ │ ├── FilesGrid.kt │ │ │ │ │ ├── SelectAllComposable.kt │ │ │ │ │ ├── StatusRowSelectAll.kt │ │ │ │ │ └── TabsContent.kt │ │ │ ├── appmanager │ │ │ │ ├── AppManagerScreen.kt │ │ │ │ ├── AppManagerViewModel.kt │ │ │ │ └── repository │ │ │ │ │ ├── AppManagerRepository.kt │ │ │ │ │ └── AppManagerRepositoryImplementation.kt │ │ │ ├── help │ │ │ │ ├── HelpActivity.kt │ │ │ │ ├── HelpScreen.kt │ │ │ │ ├── HelpViewModel.kt │ │ │ │ └── repository │ │ │ │ │ ├── HelpRepository.kt │ │ │ │ │ └── HelpRepositoryImplementation.kt │ │ │ ├── home │ │ │ │ ├── HomeScreen.kt │ │ │ │ ├── HomeViewModel.kt │ │ │ │ └── repository │ │ │ │ │ ├── HomeRepository.kt │ │ │ │ │ └── HomeRepositoryImplementation.kt │ │ │ ├── imageoptimizer │ │ │ │ ├── imageoptimizer │ │ │ │ │ ├── ImageOptimizerActivity.kt │ │ │ │ │ ├── ImageOptimizerScreen.kt │ │ │ │ │ ├── ImageOptimizerViewModel.kt │ │ │ │ │ └── tabs │ │ │ │ │ │ ├── FileSizeTab.kt │ │ │ │ │ │ ├── ManualModeTab.kt │ │ │ │ │ │ └── QuickCompressTab.kt │ │ │ │ └── imagepicker │ │ │ │ │ ├── ImagePickerActivity.kt │ │ │ │ │ ├── ImagePickerScreen.kt │ │ │ │ │ └── ImagePickerViewModel.kt │ │ │ ├── main │ │ │ │ ├── MainActivity.kt │ │ │ │ ├── MainScreen.kt │ │ │ │ ├── MainViewModel.kt │ │ │ │ └── repository │ │ │ │ │ ├── MainRepository.kt │ │ │ │ │ └── MainRepositoryImplementation.kt │ │ │ ├── memory │ │ │ │ ├── MemoryManagerScreen.kt │ │ │ │ ├── MemoryManagerViewModel.kt │ │ │ │ └── repository │ │ │ │ │ ├── MemoryManagerRepository.kt │ │ │ │ │ └── MemoryManagerRepositoryImplementation.kt │ │ │ ├── nofilesfound │ │ │ │ └── NoFilesFoundScreen.kt │ │ │ ├── settings │ │ │ │ ├── SettingsActivity.kt │ │ │ │ ├── SettingsComposable.kt │ │ │ │ ├── cleaning │ │ │ │ │ └── CleaningSettingsList.kt │ │ │ │ ├── display │ │ │ │ │ └── theme │ │ │ │ │ │ └── style │ │ │ │ │ │ ├── Color.kt │ │ │ │ │ │ └── Theme.kt │ │ │ │ ├── general │ │ │ │ │ ├── GeneralSettingsActivity.kt │ │ │ │ │ ├── GeneralSettingsScreen.kt │ │ │ │ │ └── SettingsContent.kt │ │ │ │ └── privacy │ │ │ │ │ ├── ads │ │ │ │ │ ├── AdsSettingsActivity.kt │ │ │ │ │ └── AdsSettingsScreen.kt │ │ │ │ │ └── permissions │ │ │ │ │ ├── PermissionsSettingsActivity.kt │ │ │ │ │ └── PermissionsSettingsList.kt │ │ │ ├── startup │ │ │ │ ├── StartupActivity.kt │ │ │ │ └── StartupComposable.kt │ │ │ ├── support │ │ │ │ ├── SupportActivity.kt │ │ │ │ ├── SupportScreen.kt │ │ │ │ └── SupportViewModel.kt │ │ │ └── trash │ │ │ │ ├── TrashActivity.kt │ │ │ │ ├── TrashScreen.kt │ │ │ │ └── TrashViewModel.kt │ │ └── viewmodel │ │ │ └── BaseViewModel.kt │ │ └── utils │ │ ├── cleaning │ │ ├── ImageUtils.kt │ │ └── StorageUtils.kt │ │ ├── constants │ │ ├── ads │ │ │ └── AdsConstants.kt │ │ ├── cleaning │ │ │ └── ExtensionsConstants.kt │ │ ├── datastore │ │ │ └── DataStoreNamesConstants.kt │ │ ├── permissions │ │ │ └── AppPermissionsConstants.kt │ │ └── ui │ │ │ └── bottombar │ │ │ └── BottomBarRoutes.kt │ │ ├── error │ │ └── CrashlyticsErrorReporter.kt │ │ ├── helpers │ │ ├── PermissionsHelper.kt │ │ └── TimeHelper.kt │ │ ├── imageoptimizer │ │ └── ImageOptimizerUtils.kt │ │ └── providers │ │ ├── AppAboutSettingsProvider.kt │ │ ├── AppAdvancedSettingsProvider.kt │ │ ├── AppDisplaySettingsProvider.kt │ │ ├── AppPrivacySettingsProvider.kt │ │ └── AppUsageAndDiagnosticsProvider.kt │ ├── play │ ├── contact-email.txt │ ├── contact-website.txt │ ├── default-language.txt │ ├── keys │ │ ├── com.d4rk.cleaner.jks │ │ ├── com.d4rk.cleaner.plus.jks │ │ └── com.d4rk.cleaner.plus_private_key.pepk │ └── listings │ │ └── en-US │ │ ├── full-description.txt │ │ ├── graphics │ │ ├── feature-graphic │ │ │ └── play_store_feature_graphic.png │ │ ├── ic_launcher-playstore.png │ │ └── phone-screenshots │ │ │ ├── 1-screenshot_main.png │ │ │ ├── 2-screenshot_main.png │ │ │ ├── 3-screenshot_main_memory_manager.png │ │ │ ├── 4-screenshot_main_app_manager.png │ │ │ └── 5-screenshot_trash.png │ │ ├── short-description.txt │ │ └── title.txt │ └── res │ ├── drawable-anydpi │ ├── anim_splash_screen.xml │ ├── ic_apk_document.xml │ ├── ic_archive_filter.xml │ ├── ic_audio_file.xml │ ├── ic_image.xml │ ├── ic_launcher_foreground.xml │ ├── ic_shortcut_settings_foreground.xml │ ├── ic_unknown_document.xml │ ├── ic_video_file.xml │ ├── il_settings.xml │ └── il_startup.xml │ ├── drawable-xhdpi │ └── tv_banner.png │ ├── mipmap-anydpi-v26 │ ├── ic_launcher.xml │ └── ic_shortcut_settings.xml │ ├── mipmap-hdpi │ ├── ic_launcher.webp │ └── ic_launcher_round.webp │ ├── mipmap-ldpi │ ├── ic_launcher.webp │ └── ic_launcher_round.webp │ ├── mipmap-mdpi │ ├── ic_launcher.webp │ └── ic_launcher_round.webp │ ├── mipmap-xhdpi │ ├── ic_launcher.webp │ └── ic_launcher_round.webp │ ├── mipmap-xxhdpi │ ├── ic_launcher.webp │ └── ic_launcher_round.webp │ ├── mipmap-xxxhdpi │ ├── ic_launcher.webp │ └── ic_launcher_round.webp │ ├── values-bg-rBG │ └── strings.xml │ ├── values-de-rDE │ └── strings.xml │ ├── values-es-rGQ │ └── strings.xml │ ├── values-fr-rFR │ └── strings.xml │ ├── values-hi-rIN │ └── strings.xml │ ├── values-hu-rHU │ └── strings.xml │ ├── values-in-rID │ └── strings.xml │ ├── values-it-rIT │ └── strings.xml │ ├── values-ja-rJP │ └── strings.xml │ ├── values-night-v31 │ └── colors.xml │ ├── values-night │ ├── colors.xml │ └── themes.xml │ ├── values-pl-rPL │ └── strings.xml │ ├── values-pt-rBR │ └── strings.xml │ ├── values-ro-rRO │ └── strings.xml │ ├── values-ru-rRU │ └── strings.xml │ ├── values-sv-rSE │ └── strings.xml │ ├── values-th-rTH │ └── strings.xml │ ├── values-tr-rTR │ └── strings.xml │ ├── values-uk-rUA │ └── strings.xml │ ├── values-v31 │ ├── colors.xml │ └── themes.xml │ ├── values-zh-rTW │ └── strings.xml │ ├── values │ ├── arrays.xml │ ├── colors.xml │ ├── strings.xml │ ├── themes.xml │ └── untranslatable_strings.xml │ ├── xml-v25 │ └── shortcuts.xml │ └── xml │ ├── config_locales.xml │ └── provider_paths.xml ├── build.gradle.kts ├── gradle.properties ├── gradle ├── libs.versions.toml └── wrapper │ ├── gradle-wrapper.jar │ └── gradle-wrapper.properties ├── gradlew ├── gradlew.bat └── settings.gradle.kts /.github/FUNDING.yml: -------------------------------------------------------------------------------- 1 | github: D4rK7355608 2 | patreon: patreon.com/D4rK7355608 -------------------------------------------------------------------------------- /.github/ISSUE_TEMPLATE/bug_report.yml: -------------------------------------------------------------------------------- 1 | name: Bug Report 2 | description: File a bug report 3 | title: "[Bug]: " 4 | labels: [ "bug", "triage me" ] 5 | body: 6 | - type: markdown 7 | attributes: 8 | value: | 9 | Thanks for taking the time to fill out this bug report! 10 | - type: checkboxes 11 | attributes: 12 | label: Is there an existing issue for this? 13 | description: Please search to see if an issue already exists for the bug you encountered. 14 | options: 15 | - label: I have searched the existing issues 16 | required: true 17 | - type: checkboxes 18 | attributes: 19 | label: Is there a StackOverflow question about this issue? 20 | description: Please search StackOverflow if an issue with an answer already exists for the bug you encountered. 21 | options: 22 | - label: I have searched StackOverflow 23 | required: false 24 | - type: textarea 25 | id: what-happened 26 | attributes: 27 | label: What happened? 28 | description: Also tell us, what did you expect to happen? 29 | placeholder: Tell us what you see! 30 | value: "A bug happened!" 31 | validations: 32 | required: true 33 | - type: textarea 34 | id: logs 35 | attributes: 36 | label: Relevant logcat output 37 | description: Please copy and paste any relevant logcat output. This will be automatically formatted into code, so no need for backticks. 38 | render: shell 39 | - type: checkboxes 40 | id: terms 41 | attributes: 42 | label: Code of Conduct 43 | description: By submitting this issue, you agree to follow our Code of Conduct 44 | options: 45 | - label: I agree to follow this project's Code of Conduct 46 | required: true -------------------------------------------------------------------------------- /.github/ISSUE_TEMPLATE/feature_request.yml: -------------------------------------------------------------------------------- 1 | name: Feature request 2 | description: File a feature request 3 | title: "[FR]: " 4 | labels: [ "enhancement", "triage me" ] 5 | body: 6 | - type: markdown 7 | attributes: 8 | value: | 9 | Thanks for taking the time to fill out this bug report! 10 | - type: checkboxes 11 | attributes: 12 | label: Is there an existing issue for this? 13 | description: Please search to see if an issue already exists for this feature request. 14 | options: 15 | - label: I have searched the existing issues 16 | required: true 17 | - type: textarea 18 | id: describe-problem 19 | attributes: 20 | label: Describe the problem 21 | description: Is your feature request related to a problem? Please describe. 22 | placeholder: I'm always frustrated when... 23 | validations: 24 | required: true 25 | - type: textarea 26 | id: solution 27 | attributes: 28 | label: Describe the solution 29 | description: Please describe the solution you'd like. A clear and concise description of what you want to happen. 30 | validations: 31 | required: true 32 | - type: textarea 33 | id: context 34 | attributes: 35 | label: Additional context 36 | description: Add any other context or screenshots about the feature request here. 37 | validations: 38 | required: false 39 | - type: checkboxes 40 | id: terms 41 | attributes: 42 | label: Code of Conduct 43 | description: By submitting this issue, you agree to follow our [Code of Conduct](CODE_OF_CONDUCT.md) 44 | options: 45 | - label: I agree to follow this project's Code of Conduct 46 | required: true -------------------------------------------------------------------------------- /.github/dependabot.yml: -------------------------------------------------------------------------------- 1 | version: 2 2 | updates: 3 | - package-ecosystem: gradle 4 | directory: "/" 5 | schedule: 6 | interval: weekly 7 | time: "11:00" 8 | open-pull-requests-limit: 10 9 | -------------------------------------------------------------------------------- /.github/workflows/android.yml: -------------------------------------------------------------------------------- 1 | name: Android CI 2 | 3 | on: 4 | push: 5 | branches: [ "master" ] 6 | pull_request: 7 | branches: [ "master" ] 8 | 9 | jobs: 10 | build: 11 | 12 | runs-on: ubuntu-latest 13 | 14 | steps: 15 | - uses: actions/checkout@v3 16 | - name: set up JDK 21 17 | uses: actions/setup-java@v3 18 | with: 19 | java-version: '21' 20 | distribution: 'temurin' 21 | cache: gradle 22 | 23 | - name: Grant execute permission for gradlew 24 | run: chmod +x gradlew 25 | - name: Build with Gradle 26 | run: ./gradlew build 27 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | ############################################################################### 2 | # BUILD OUTPUTS 3 | ############################################################################### 4 | # Gradle build cache and output 5 | .gradle/ 6 | build/ 7 | generated/ 8 | 9 | # Ignore generated binaries and other typical build artifacts 10 | bin/ 11 | gen/ 12 | out/ 13 | 14 | # Android packaged/bundled application files 15 | *.apk 16 | *.aab 17 | 18 | # Dalvik/ART VM bytecode 19 | *.dex 20 | 21 | # Java/Kotlin class files 22 | *.class 23 | 24 | # Release artifacts (uncomment or adjust as needed) 25 | app/release/* 26 | release/ 27 | 28 | ############################################################################### 29 | # LOCAL CONFIG / ENV FILES 30 | ############################################################################### 31 | # SDK path and other local configurations 32 | local.properties 33 | 34 | # Proguard folder generated by Eclipse 35 | proguard/ 36 | 37 | ############################################################################### 38 | # LOG / OS FILES 39 | ############################################################################### 40 | # Log files 41 | *.log 42 | 43 | # macOS Finder metadata 44 | .DS_Store 45 | 46 | # Windows thumbnail cache files (if needed) 47 | Thumbs.db 48 | 49 | ############################################################################### 50 | # ANDROID STUDIO / INTELLIJ 51 | ############################################################################### 52 | # Android Studio captures folder 53 | captures/ 54 | 55 | # Android Studio generated external native build folders 56 | .externalNativeBuild/ 57 | .cxx/ 58 | 59 | # Gradle user-specific settings in IntelliJ/Android Studio 60 | *.iml 61 | .idea/ 62 | # If you'd like to keep your code style settings in VCS, leave the negated lines 63 | .idea/* 64 | !.idea/codeStyles 65 | /.idea/codeStyles/* 66 | !/.idea/codeStyles/Project.xml 67 | !/.idea/codeStyles/codeStyleConfig.xml 68 | 69 | # Other IntelliJ/AS config files 70 | misc.xml 71 | deploymentTargetDropDown.xml 72 | render.experimental.xml 73 | .navigation/ 74 | .idea/libraries 75 | .idea/caches 76 | .idea/modules.xml 77 | .idea/navEditor.xml 78 | 79 | ############################################################################### 80 | # ANDROID PROFILING 81 | ############################################################################### 82 | *.hprof 83 | 84 | ############################################################################### 85 | # VERSION CONTROL SYSTEM 86 | ############################################################################### 87 | vcs.xml 88 | 89 | ############################################################################### 90 | # LINT 91 | ############################################################################### 92 | lint/intermediates/ 93 | lint/generated/ 94 | lint/outputs/ 95 | lint/tmp/ 96 | 97 | ############################################################################### 98 | # KOTLIN 99 | ############################################################################### 100 | .kotlin -------------------------------------------------------------------------------- /.idea/.name: -------------------------------------------------------------------------------- 1 | Cleaner for Android -------------------------------------------------------------------------------- /.idea/appInsightsSettings.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 40 | 41 | -------------------------------------------------------------------------------- /.idea/compiler.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | -------------------------------------------------------------------------------- /.idea/deploymentTargetDropDown.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | -------------------------------------------------------------------------------- /.idea/deploymentTargetSelector.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 16 | 17 | 18 | -------------------------------------------------------------------------------- /.idea/gradle.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 19 | 20 | -------------------------------------------------------------------------------- /.idea/icon.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 5 | 14 | 15 | 17 | 19 | 20 | 22 | 24 | 28 | 29 | 30 | 31 | 32 | 33 | -------------------------------------------------------------------------------- /.idea/inspectionProfiles/Project_Default.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 70 | -------------------------------------------------------------------------------- /.idea/kotlinc.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 6 | -------------------------------------------------------------------------------- /.idea/migrations.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 9 | 10 | -------------------------------------------------------------------------------- /.idea/misc.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 15 | -------------------------------------------------------------------------------- /.idea/studiobot.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 6 | -------------------------------------------------------------------------------- /.idea/vcs.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | -------------------------------------------------------------------------------- /EULA.md: -------------------------------------------------------------------------------- 1 | # End-User License Agreement (EULA) 2 | 3 | This End-User License Agreement ("EULA") governs your use of any and all software applications 4 | developed and distributed by D4rK. By installing, 5 | copying, using, or redistributing the Software, you acknowledge that you have read, understood, and 6 | agree to be bound by the terms of this EULA. 7 | 8 | ## License Grant 9 | 10 | The Developer grants you a non-exclusive, revocable, worldwide, royalty-free license to use the 11 | Software for personal, non-commercial purposes, subject to the limitations set forth in this EULA. 12 | Unless otherwise expressly stated in writing by the Developer, you may not: 13 | 14 | * Sublicense, distribute, or transfer the Software to any third party. 15 | * Modify, adapt, translate, reverse engineer, decompile, or disassemble the Software. 16 | * Remove, alter, or obscure any copyright, trademark, or other proprietary notices contained in the 17 | Software. 18 | * Use the Software in any way that violates any applicable laws or regulations. 19 | * Use the Software to engage in any illegal, harmful, or offensive activity. 20 | * Use the Software to infringe the intellectual property rights of any third party. 21 | 22 | ## Intellectual Property 23 | 24 | The Software and all related intellectual property rights, including but not limited to copyrights, 25 | trademarks, and trade secrets, are owned by the Developer. This EULA does not grant you any 26 | ownership rights in the Software. 27 | 28 | ## Disclaimer of Warranties 29 | 30 | THE SOFTWARE IS PROVIDED "AS IS," WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT 31 | NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE, AND 32 | NON-INFRINGEMENT. IN NO EVENT SHALL THE DEVELOPER BE LIABLE FOR ANY CLAIM, DAMAGES, OR OTHER 33 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT, OR OTHERWISE, ARISING FROM, OUT OF, OR IN 34 | CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. THE DEVELOPER DOES NOT 35 | WARRANT THAT THE SOFTWARE WILL BE ERROR-FREE, UNINTERRUPTED, OR FREE OF VIRUSES OR OTHER HARMFUL 36 | COMPONENTS. 37 | 38 | ## Limitation of Liability 39 | 40 | IN NO EVENT SHALL THE DEVELOPER BE LIABLE FOR ANY INDIRECT, INCIDENTAL, SPECIAL, CONSEQUENTIAL, OR 41 | EXEMPLARY DAMAGES, INCLUDING BUT NOT LIMITED TO DAMAGES FOR LOSS OF PROFITS, DATA, OR USE, ARISING 42 | OUT OF OR IN CONNECTION WITH THE SOFTWARE, EVEN IF THE DEVELOPER HAS BEEN ADVISED OF THE POSSIBILITY 43 | OF SUCH DAMAGES. 44 | 45 | ## Term and Termination 46 | 47 | This EULA is effective upon your acceptance and shall continue until terminated. Your rights under 48 | this EULA will terminate automatically without notice from the Developer if you breach any term of 49 | this EULA. Upon termination, you must cease all use of the Software and destroy all copies in your 50 | possession. 51 | 52 | ## Governing Law 53 | 54 | This EULA shall be governed by and construed in accordance with the laws of Romania. Any dispute 55 | arising out of or in connection with this EULA shall be subject to the exclusive jurisdiction of the 56 | competent courts of Bucharest, Romania. 57 | 58 | ## Contact Information 59 | 60 | If you have any questions or concerns regarding this EULA, please contact the Developer 61 | at d4rk7355608@gmail.com. 62 | 63 | ## Open Source Components 64 | 65 | This Software may incorporate or utilize certain open-source components. These components are 66 | governed by their respective licenses, which can be 67 | found 'About' or 'Legal' section of the app. Your use of these components is 68 | subject to the terms of their respective licenses. 69 | 70 | ## Changes to this EULA 71 | 72 | The Developer reserves the right to modify this EULA at any time. Any changes will be effective upon 73 | posting the revised EULA at https://github.com/D4rK7355608/com.d4rk.cleaner/EULA.md. Your 74 | continued use of the Software after the effective date of any changes constitutes your acceptance of 75 | the revised EULA. -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | ![Cleaner for Android](/app/src/main/play/listings/en-US/graphics/feature-graphic/play_store_feature_graphic.png "Cleaner for Android") 2 | 3 | 4 | 5 | Cleaner for Android 6 | ================== 7 | 8 | **Easy to use one-tap phone cleaner. Say hello to a lightning-fast device!** 9 | 10 | Cleaner is an Android app that helps you free up space and manage your device. It removes junk 11 | files, cache and other unwanted data from your device, so you can get back the storage and 12 | performance you need. 13 | 14 | It includes a variety of other features to help you keep your phone clean and running smoothly, such 15 | as a clipboard cleaner, app manager, whitelist, and customizable cleaning filters. With Cleaner, you 16 | can easily keep your phone in top condition, without having to worry about complicated settings or 17 | rooting your device. 18 | 19 | Our app is designed to be simple and easy to use, while also being fast and lightweight. Plus, it's 20 | free and open-source software! 21 | 22 | # Features 23 | 24 | - App management 25 | - Image optimization 26 | - Clean empty folders 27 | - Clean logs, temporary files, caches, and corpse files 28 | - Clean advertisement folders 29 | - Clean archive files 30 | - Clean invalid media 31 | - Clean APK files 32 | - Clean clipboard contents 33 | 34 | # Benefits 35 | 36 | - Free up space on your device 37 | - Improve performance 38 | - Keep your device organized and running smoothly 39 | - Protect your privacy by removing sensitive data 40 | 41 | # Screenshots 42 | 43 | 44 | 45 | 46 | 47 | 48 | 49 |
50 | 51 | # How it works 52 | 53 | Cleaner scans your device for junk files, cache, and other unwanted data. It then gives you the 54 | option to remove these files, or to add them to a whitelist so that they are not removed in the 55 | future. You can also schedule Cleaner to run automatically on a daily basis, so you can keep your 56 | device clean and running smoothly without any effort. 57 | 58 | # Get started today 59 | 60 | Download Cleaner from the Google Play Store today and start freeing up space and improving the 61 | performance of your Android device. It's free and easy to use, and it's the perfect way to keep your 62 | device running at its best. 63 | 64 | # Feedback 65 | 66 | We are constantly updating and improving Cleaner to give you the best possible experience. If you 67 | have any suggested features or improvements, please leave a review. In case something is not working 68 | correctly please let me know. When posting a low rating please describe what is wrong to give the 69 | possibility to fix that issue. 70 | 71 | Thank you for choosing Cleaner! We hope you enjoy using our app as much as we enjoyed creating it 72 | for you! 73 | 74 | # License 75 | 76 | __Privacy Policy__ [here](https://sites.google.com/view/d4rk7355608/more/apps/privacy-policy). 77 | __Terms of Service__ [here](https://sites.google.com/view/d4rk7355608/more/apps/terms-of-service). 78 | 79 | ![license](https://imgur.com/QQlcEVT.png) -------------------------------------------------------------------------------- /app/.gitignore: -------------------------------------------------------------------------------- 1 | /build -------------------------------------------------------------------------------- /app/build.gradle.kts: -------------------------------------------------------------------------------- 1 | plugins { 2 | alias(notation= libs.plugins.androidApplication) 3 | alias(notation= libs.plugins.jetbrainsKotlinAndroid) 4 | alias(notation= libs.plugins.googlePlayServices) 5 | alias(notation= libs.plugins.googleFirebase) 6 | alias(notation= libs.plugins.compose.compiler) 7 | alias(notation= libs.plugins.about.libraries) 8 | } 9 | 10 | android { 11 | compileSdk = 35 12 | namespace = "com.d4rk.cleaner" 13 | defaultConfig { 14 | applicationId = "com.d4rk.cleaner" 15 | minSdk = 23 16 | targetSdk = 35 17 | versionCode = 158 18 | versionName = "3.2.4" 19 | testInstrumentationRunner = "androidx.test.runner.AndroidJUnitRunner" 20 | @Suppress("UnstableApiUsage") 21 | androidResources.localeFilters += listOf( 22 | "en" , 23 | "bg-rBG" , 24 | "de-rDE" , 25 | "es-rGQ" , 26 | "fr-rFR" , 27 | "hi-rIN" , 28 | "hu-rHU" , 29 | "in-rID" , 30 | "it-rIT" , 31 | "ja-rJP" , 32 | "pl-rPL" , 33 | "pt-rBR" , 34 | "ro-rRO" , 35 | "ru-rRU" , 36 | "sv-rSE" , 37 | "th-rTH" , 38 | "tr-rTR" , 39 | "uk-rUA" , 40 | "zh-rTW" , 41 | ) 42 | vectorDrawables { 43 | useSupportLibrary = true 44 | } 45 | } 46 | 47 | buildTypes { 48 | release { 49 | isDebuggable = false 50 | } 51 | debug { 52 | isDebuggable = true 53 | } 54 | } 55 | 56 | buildTypes.forEach { buildType -> 57 | with(buildType) { 58 | multiDexEnabled = true 59 | proguardFiles( 60 | getDefaultProguardFile(name = "proguard-android-optimize.txt") , 61 | "proguard-rules.pro" 62 | ) 63 | } 64 | } 65 | 66 | compileOptions { 67 | sourceCompatibility = JavaVersion.VERSION_21 68 | targetCompatibility = JavaVersion.VERSION_21 69 | } 70 | 71 | kotlinOptions { 72 | jvmTarget = "21" 73 | } 74 | 75 | buildFeatures { 76 | buildConfig = true 77 | compose = true 78 | } 79 | 80 | packaging { 81 | resources { 82 | excludes += "/META-INF/{AL2.0,LGPL2.1}" 83 | } 84 | } 85 | 86 | bundle { 87 | storeArchive { 88 | enable = true 89 | } 90 | } 91 | } 92 | 93 | dependencies { 94 | 95 | // App Core 96 | implementation(dependencyNotation = "com.github.D4rK7355608:AppToolkit:0.0.70") { 97 | isTransitive = true 98 | } 99 | 100 | implementation(dependencyNotation = libs.androidx.constraintlayout.compose) 101 | 102 | // Image Compression 103 | implementation(dependencyNotation = libs.compressor) 104 | implementation(dependencyNotation = libs.coil3.coil.video) 105 | } -------------------------------------------------------------------------------- /app/proguard-rules.pro: -------------------------------------------------------------------------------- 1 | # Add project specific ProGuard rules here. 2 | # You can control the set of applied configuration files using the 3 | # proguardFiles setting in build.gradle. 4 | # 5 | # For more details, see 6 | # http://developer.android.com/guide/developing/tools/proguard.html 7 | 8 | # If your project uses WebView with JS, uncomment the following 9 | # and specify the fully qualified class name to the JavaScript interface 10 | # class: 11 | #-keepclassmembers class fqcn.of.javascript.interface.for.webview { 12 | # public *; 13 | #} 14 | 15 | # Uncomment this to preserve the line number information for 16 | # debugging stack traces. 17 | #-keepattributes SourceFile,LineNumberTable 18 | 19 | # If you keep the line number information, uncomment this to 20 | # hide the original source file name. 21 | #-renamesourcefileattribute SourceFile -------------------------------------------------------------------------------- /app/src/main/kotlin/com/d4rk/cleaner/data/core/AppCoreManager.kt: -------------------------------------------------------------------------------- 1 | @file:Suppress("DEPRECATION") 2 | 3 | package com.d4rk.cleaner.data.core 4 | 5 | import android.annotation.SuppressLint 6 | import android.app.Activity 7 | import android.os.Bundle 8 | import androidx.lifecycle.Lifecycle 9 | import androidx.lifecycle.OnLifecycleEvent 10 | import androidx.lifecycle.ProcessLifecycleOwner 11 | import com.d4rk.android.libs.apptoolkit.data.core.BaseCoreManager 12 | import com.d4rk.android.libs.apptoolkit.data.core.ads.AdsCoreManager 13 | import com.d4rk.android.libs.apptoolkit.utils.error.ErrorHandler 14 | import com.d4rk.cleaner.utils.constants.ads.AdsConstants 15 | import com.d4rk.cleaner.data.datastore.DataStore 16 | import com.d4rk.cleaner.utils.error.CrashlyticsErrorReporter 17 | import kotlinx.coroutines.Deferred 18 | import kotlinx.coroutines.async 19 | import kotlinx.coroutines.supervisorScope 20 | 21 | class AppCoreManager : BaseCoreManager() { 22 | 23 | private var currentActivity : Activity? = null 24 | 25 | companion object { 26 | @SuppressLint("StaticFieldLeak") 27 | lateinit var instance : AppCoreManager 28 | private set 29 | 30 | lateinit var dataStore : DataStore 31 | private set 32 | 33 | val adsCoreManager : AdsCoreManager by lazy { 34 | AdsCoreManager(context = instance) 35 | } 36 | 37 | val isAppLoaded : Boolean 38 | get() = BaseCoreManager.isAppLoaded 39 | } 40 | 41 | override fun onCreate() { 42 | super.onCreate() 43 | instance = this 44 | 45 | val crashlyticsReporter = CrashlyticsErrorReporter() 46 | ErrorHandler.init(reporter = crashlyticsReporter) 47 | 48 | registerActivityLifecycleCallbacks(this) 49 | ProcessLifecycleOwner.get().lifecycle.addObserver(observer = this) 50 | } 51 | 52 | override suspend fun onInitializeApp() = supervisorScope { 53 | val dataStoreInitialization : Deferred = async { initializeDataStore() } 54 | val adsInitialization : Deferred = async { initializeAds() } 55 | 56 | dataStoreInitialization.await() 57 | adsInitialization.await() 58 | } 59 | 60 | private fun initializeDataStore() { 61 | runCatching { 62 | dataStore = DataStore.getInstance(context = this@AppCoreManager) 63 | }.onFailure { 64 | ErrorHandler.handleInitializationFailure( 65 | message = "DataStore initialization failed" , exception = it as Exception , applicationContext = applicationContext 66 | ) 67 | } 68 | } 69 | 70 | private fun initializeAds() { 71 | adsCoreManager.initializeAds(AdsConstants.APP_OPEN_UNIT_ID) 72 | } 73 | 74 | fun isAppLoaded() : Boolean { 75 | return isAppLoaded 76 | } 77 | 78 | @OnLifecycleEvent(Lifecycle.Event.ON_START) 79 | fun onMoveToForeground() { 80 | currentActivity?.let { adsCoreManager.showAdIfAvailable(it) } 81 | } 82 | 83 | override fun onActivityCreated(activity : Activity , savedInstanceState : Bundle?) {} 84 | 85 | override fun onActivityStarted(activity : Activity) { 86 | currentActivity = activity 87 | } 88 | 89 | override fun onActivityResumed(activity : Activity) {} 90 | override fun onActivityPaused(activity : Activity) {} 91 | override fun onActivityStopped(activity : Activity) {} 92 | override fun onActivitySaveInstanceState(activity : Activity , outState : Bundle) {} 93 | override fun onActivityDestroyed(activity : Activity) {} 94 | } -------------------------------------------------------------------------------- /app/src/main/kotlin/com/d4rk/cleaner/data/model/ui/appmanager/ui/ApkInfo.kt: -------------------------------------------------------------------------------- 1 | package com.d4rk.cleaner.data.model.ui.appmanager.ui 2 | 3 | data class ApkInfo( 4 | val id : Long , val path : String , val size : Long 5 | ) -------------------------------------------------------------------------------- /app/src/main/kotlin/com/d4rk/cleaner/data/model/ui/imageoptimizer/CompressionLevel.kt: -------------------------------------------------------------------------------- 1 | package com.d4rk.cleaner.data.model.ui.imageoptimizer 2 | 3 | import com.d4rk.cleaner.R 4 | 5 | enum class CompressionLevel(val stringRes : Int , val defaultPercentage : Int) { 6 | LOW(R.string.low , defaultPercentage = 25) , MEDIUM( 7 | R.string.medium , defaultPercentage = 50 8 | ) , 9 | HIGH(R.string.high , defaultPercentage = 75) 10 | } -------------------------------------------------------------------------------- /app/src/main/kotlin/com/d4rk/cleaner/data/model/ui/imageoptimizer/ImageOptimizerState.kt: -------------------------------------------------------------------------------- 1 | package com.d4rk.cleaner.data.model.ui.imageoptimizer 2 | 3 | import android.net.Uri 4 | 5 | data class ImageOptimizerState( 6 | val selectedImageUri : Uri? = null , 7 | val compressedImageUri : Uri? = null , 8 | val compressedSizeKB : Double = 0.0 , 9 | val isLoading : Boolean = false , 10 | val quickCompressValue : Int = 50 , 11 | val fileSizeKB : Int = 0 , 12 | val originalWidth : Int = 0 , 13 | val originalHeight : Int = 0 , 14 | val manualWidth : Int = 0 , 15 | val manualHeight : Int = 0 , 16 | val manualQuality : Int = 50 , 17 | val currentTab : Int = 0 , 18 | val showSaveSnackbar : Boolean = false 19 | ) -------------------------------------------------------------------------------- /app/src/main/kotlin/com/d4rk/cleaner/data/model/ui/memorymanager/InternalStorageInfo.kt: -------------------------------------------------------------------------------- 1 | package com.d4rk.cleaner.data.model.ui.memorymanager 2 | 3 | data class InternalStorageInfo( 4 | val totalStorage : Long , val freeStorage : Long , val usedStorage : Long 5 | ) -------------------------------------------------------------------------------- /app/src/main/kotlin/com/d4rk/cleaner/data/model/ui/memorymanager/RamInfo.kt: -------------------------------------------------------------------------------- 1 | package com.d4rk.cleaner.data.model.ui.memorymanager 2 | 3 | data class RamInfo( 4 | val totalRam : Long = 0 , val availableRam : Long = 0 , val usedRam : Long = 0 5 | ) -------------------------------------------------------------------------------- /app/src/main/kotlin/com/d4rk/cleaner/data/model/ui/memorymanager/StorageInfo.kt: -------------------------------------------------------------------------------- 1 | package com.d4rk.cleaner.data.model.ui.memorymanager 2 | 3 | data class StorageInfo( 4 | val storageUsageProgress : Float = 0f , 5 | val freeStorage : Long = 0 , 6 | val usedStorage : Long = 0 , 7 | val freeSpacePercentage : Int = 0 , 8 | val storageBreakdown : Map = emptyMap() , 9 | val usedStorageFormatted : String = "" , 10 | val totalStorageFormatted : String = "" , 11 | val cleanedSpace : String = "0 KB" , 12 | ) -------------------------------------------------------------------------------- /app/src/main/kotlin/com/d4rk/cleaner/data/model/ui/navigation/BottomNavigationScreen.kt: -------------------------------------------------------------------------------- 1 | package com.d4rk.cleaner.data.model.ui.navigation 2 | 3 | import androidx.compose.material.icons.Icons 4 | import androidx.compose.material.icons.filled.Home 5 | import androidx.compose.material.icons.outlined.Home 6 | import androidx.compose.material.icons.rounded.AppRegistration 7 | import androidx.compose.material.icons.rounded.Storage 8 | import androidx.compose.material.icons.sharp.AppRegistration 9 | import androidx.compose.material.icons.sharp.Storage 10 | import androidx.compose.ui.graphics.vector.ImageVector 11 | import com.d4rk.cleaner.R 12 | import com.d4rk.cleaner.utils.constants.ui.bottombar.BottomBarRoutes 13 | 14 | sealed class BottomNavigationScreen( 15 | val route : String , val icon : ImageVector , val selectedIcon : ImageVector , val title : Int 16 | ) { 17 | data object Home : BottomNavigationScreen( 18 | BottomBarRoutes.HOME , Icons.Outlined.Home , Icons.Filled.Home , com.d4rk.android.libs.apptoolkit.R.string.home 19 | ) 20 | 21 | data object AppManager : BottomNavigationScreen( 22 | BottomBarRoutes.APP_MANAGER , Icons.Sharp.AppRegistration , Icons.Rounded.AppRegistration , R.string.app_manager 23 | ) 24 | 25 | data object MemoryManager : BottomNavigationScreen( 26 | BottomBarRoutes.MEMORY_MANAGER , Icons.Sharp.Storage , Icons.Rounded.Storage , R.string.memory_manager 27 | ) 28 | } -------------------------------------------------------------------------------- /app/src/main/kotlin/com/d4rk/cleaner/data/model/ui/screens/MainScreenState.kt: -------------------------------------------------------------------------------- 1 | package com.d4rk.cleaner.data.model.ui.screens 2 | 3 | import android.content.Context 4 | import android.view.View 5 | import androidx.compose.material3.DrawerState 6 | import androidx.navigation.NavHostController 7 | import com.d4rk.cleaner.data.datastore.DataStore 8 | import com.d4rk.cleaner.ui.screens.main.MainViewModel 9 | 10 | data class MainScreenState( 11 | val context: Context , 12 | val view: View , 13 | val drawerState: DrawerState , 14 | val navHostController: NavHostController , 15 | val dataStore: DataStore , 16 | val viewModel: MainViewModel 17 | ) -------------------------------------------------------------------------------- /app/src/main/kotlin/com/d4rk/cleaner/data/model/ui/screens/UiAppManagerModel.kt: -------------------------------------------------------------------------------- 1 | package com.d4rk.cleaner.data.model.ui.screens 2 | 3 | import android.content.pm.ApplicationInfo 4 | import com.d4rk.cleaner.data.model.ui.appmanager.ui.ApkInfo 5 | 6 | data class UiAppManagerModel( 7 | val installedApps : List = emptyList() , 8 | val apkFiles : List = emptyList() , 9 | ) -------------------------------------------------------------------------------- /app/src/main/kotlin/com/d4rk/cleaner/data/model/ui/screens/UiHelpScreen.kt: -------------------------------------------------------------------------------- 1 | package com.d4rk.cleaner.data.model.ui.screens 2 | 3 | import com.google.android.play.core.review.ReviewInfo 4 | 5 | data class UiHelpScreen( 6 | var reviewInfo : ReviewInfo? = null , val questions : ArrayList = ArrayList() 7 | ) 8 | 9 | data class UiHelpQuestion( 10 | val question : String = "" , val answer : String = "" , val isExpanded : Boolean = false 11 | ) -------------------------------------------------------------------------------- /app/src/main/kotlin/com/d4rk/cleaner/data/model/ui/screens/UiHomeModel.kt: -------------------------------------------------------------------------------- 1 | package com.d4rk.cleaner.data.model.ui.screens 2 | 3 | import com.d4rk.cleaner.data.model.ui.memorymanager.StorageInfo 4 | import java.io.File 5 | 6 | data class UiHomeModel( 7 | val storageInfo : StorageInfo = StorageInfo() , 8 | var analyzeState : UiAnalyzeModel = UiAnalyzeModel() , 9 | var daysFromLastScan : Int = 0 , 10 | var isRescanDialogVisible : Boolean = false , 11 | ) 12 | 13 | data class UiAnalyzeModel( 14 | var isAnalyzeScreenVisible : Boolean = false , 15 | var scannedFileList : List = emptyList() , 16 | var emptyFolderList : List = emptyList() , 17 | var areAllFilesSelected : Boolean = false , 18 | var fileSelectionMap : Map = emptyMap() , 19 | var selectedFilesCount : Int = 0 , 20 | var groupedFiles : Map> = emptyMap() , 21 | var fileTypesData : FileTypesData = FileTypesData() , 22 | var isDeleteForeverConfirmationDialogVisible : Boolean = false , 23 | var isMoveToTrashConfirmationDialogVisible : Boolean = false , 24 | ) 25 | 26 | data class FileTypesData( 27 | var fileTypesTitles : List = emptyList() , 28 | var apkExtensions : List = emptyList() , 29 | var imageExtensions : List = emptyList() , 30 | var videoExtensions : List = emptyList() , 31 | var audioExtensions : List = emptyList() , 32 | var archiveExtensions : List = emptyList() , 33 | var fontExtensions : List = emptyList() , 34 | var windowsExtensions : List = emptyList() , 35 | var officeExtensions : List = emptyList() , 36 | var otherExtensions : List = emptyList() , 37 | ) -------------------------------------------------------------------------------- /app/src/main/kotlin/com/d4rk/cleaner/data/model/ui/screens/UiMainScreen.kt: -------------------------------------------------------------------------------- 1 | package com.d4rk.cleaner.data.model.ui.screens 2 | 3 | import com.d4rk.android.libs.apptoolkit.data.model.ui.navigation.NavigationDrawerItem 4 | import com.d4rk.cleaner.data.model.ui.navigation.BottomNavigationScreen 5 | 6 | data class UiMainScreen( 7 | val navigationDrawerItems : List = listOf() , 8 | val bottomNavigationItems : List = listOf() , 9 | val currentBottomNavigationScreen : BottomNavigationScreen = BottomNavigationScreen.Home , 10 | val trashSize : String = "0 KB" , 11 | ) -------------------------------------------------------------------------------- /app/src/main/kotlin/com/d4rk/cleaner/data/model/ui/screens/UiMemoryManagerModel.kt: -------------------------------------------------------------------------------- 1 | package com.d4rk.cleaner.data.model.ui.screens 2 | 3 | import com.d4rk.cleaner.data.model.ui.memorymanager.RamInfo 4 | import com.d4rk.cleaner.data.model.ui.memorymanager.StorageInfo 5 | 6 | data class UiMemoryManagerModel( 7 | val storageInfo : StorageInfo = StorageInfo() , 8 | val ramInfo : RamInfo = RamInfo() , 9 | val listExpanded : Boolean = true , 10 | ) -------------------------------------------------------------------------------- /app/src/main/kotlin/com/d4rk/cleaner/data/model/ui/screens/UiTrashModel.kt: -------------------------------------------------------------------------------- 1 | package com.d4rk.cleaner.data.model.ui.screens 2 | 3 | import java.io.File 4 | 5 | data class UiTrashModel( 6 | val trashFiles : List = emptyList() , 7 | val selectedFileCount : Int = 0 , 8 | val fileSelectionStates : Map = emptyMap() , 9 | ) -------------------------------------------------------------------------------- /app/src/main/kotlin/com/d4rk/cleaner/ui/components/ads/BannerAds.kt: -------------------------------------------------------------------------------- 1 | package com.d4rk.cleaner.ui.components.ads 2 | 3 | import androidx.compose.foundation.layout.fillMaxWidth 4 | import androidx.compose.foundation.layout.height 5 | import androidx.compose.runtime.Composable 6 | import androidx.compose.runtime.collectAsState 7 | import androidx.compose.runtime.getValue 8 | import androidx.compose.ui.Modifier 9 | import androidx.compose.ui.unit.dp 10 | import androidx.compose.ui.viewinterop.AndroidView 11 | import com.d4rk.cleaner.utils.constants.ads.AdsConstants 12 | import com.d4rk.cleaner.data.core.AppCoreManager 13 | import com.google.android.gms.ads.AdRequest 14 | import com.google.android.gms.ads.AdSize 15 | import com.google.android.gms.ads.AdView 16 | 17 | @Composable 18 | fun AdBanner(modifier : Modifier = Modifier , adSize : AdSize = AdSize.BANNER) { 19 | val showAds : Boolean by AppCoreManager.dataStore.ads.collectAsState(initial = true) 20 | 21 | if (showAds) { 22 | AndroidView(modifier = modifier 23 | .fillMaxWidth() 24 | .height(height = adSize.height.dp) , factory = { context -> 25 | AdView(context).apply { 26 | setAdSize(adSize) 27 | adUnitId = AdsConstants.BANNER_AD_UNIT_ID 28 | loadAd(AdRequest.Builder().build()) 29 | } 30 | }) 31 | } 32 | } -------------------------------------------------------------------------------- /app/src/main/kotlin/com/d4rk/cleaner/ui/components/buttons/Buttons.kt: -------------------------------------------------------------------------------- 1 | package com.d4rk.cleaner.ui.components.buttons 2 | 3 | import android.view.SoundEffectConstants 4 | import android.view.View 5 | import androidx.compose.foundation.basicMarquee 6 | import androidx.compose.foundation.layout.Arrangement 7 | import androidx.compose.foundation.layout.Row 8 | import androidx.compose.foundation.layout.Spacer 9 | import androidx.compose.foundation.layout.fillMaxWidth 10 | import androidx.compose.foundation.layout.size 11 | import androidx.compose.foundation.layout.width 12 | import androidx.compose.material3.Button 13 | import androidx.compose.material3.ButtonDefaults 14 | import androidx.compose.material3.Icon 15 | import androidx.compose.material3.OutlinedButton 16 | import androidx.compose.material3.Text 17 | import androidx.compose.runtime.Composable 18 | import androidx.compose.ui.Modifier 19 | import androidx.compose.ui.graphics.vector.ImageVector 20 | import androidx.compose.ui.res.stringResource 21 | import androidx.compose.ui.unit.dp 22 | import com.d4rk.android.libs.apptoolkit.ui.components.modifiers.bounceClick 23 | import com.d4rk.android.libs.apptoolkit.ui.components.spacers.ButtonIconSpacer 24 | 25 | @Composable 26 | fun TwoRowButtons( 27 | modifier : Modifier , enabled : Boolean , onStartButtonClick : () -> Unit , onStartButtonIcon : ImageVector , onStartButtonText : Int , onEndButtonClick : () -> Unit , onEndButtonIcon : ImageVector , onEndButtonText : Int , view : View 28 | ) { 29 | 30 | Row( 31 | modifier = modifier.fillMaxWidth() , horizontalArrangement = Arrangement.SpaceAround 32 | ) { 33 | OutlinedButton( 34 | enabled = enabled , 35 | onClick = { 36 | view.playSoundEffect(SoundEffectConstants.CLICK) 37 | onStartButtonClick() 38 | } , 39 | modifier = Modifier 40 | .weight(weight = 1f) 41 | .bounceClick() , 42 | ) { 43 | Icon( 44 | imageVector = onStartButtonIcon , contentDescription = "Move to trash" , modifier = Modifier.size(size = ButtonDefaults.IconSize) 45 | ) 46 | ButtonIconSpacer() 47 | Text(text = stringResource(id = onStartButtonText) , modifier = Modifier.basicMarquee()) 48 | } 49 | 50 | Spacer(Modifier.width(width = 8.dp)) 51 | 52 | Button( 53 | enabled = enabled , 54 | onClick = { 55 | view.playSoundEffect(SoundEffectConstants.CLICK) 56 | onEndButtonClick() 57 | } , 58 | modifier = Modifier 59 | .weight(1f) 60 | .bounceClick() , 61 | ) { 62 | Icon( 63 | imageVector = onEndButtonIcon , contentDescription = "Delete forever" , modifier = Modifier.size(size = ButtonDefaults.IconSize) 64 | ) 65 | ButtonIconSpacer() 66 | Text(text = stringResource(id = onEndButtonText) , modifier = Modifier.basicMarquee()) 67 | } 68 | } 69 | } -------------------------------------------------------------------------------- /app/src/main/kotlin/com/d4rk/cleaner/ui/components/dialogs/ConfirmationAlertDialog.kt: -------------------------------------------------------------------------------- 1 | package com.d4rk.cleaner.ui.components.dialogs 2 | 3 | import androidx.compose.material3.AlertDialog 4 | import androidx.compose.material3.Text 5 | import androidx.compose.material3.TextButton 6 | import androidx.compose.runtime.Composable 7 | 8 | @Composable 9 | fun ConfirmationAlertDialog( 10 | confirmationTitle : String , confirmationMessage : String , confirmationConfirmButtonText : String , confirmationDismissButtonText : String , onConfirm : () -> Unit , onDismiss : () -> Unit 11 | ) { 12 | AlertDialog(onDismissRequest = onDismiss , title = { Text(text = confirmationTitle) } , text = { Text(text = confirmationMessage) } , confirmButton = { 13 | TextButton(onClick = onConfirm) { 14 | Text(text = confirmationConfirmButtonText) 15 | } 16 | } , dismissButton = { 17 | TextButton(onClick = onDismiss) { 18 | Text(text = confirmationDismissButtonText) 19 | } 20 | }) 21 | } -------------------------------------------------------------------------------- /app/src/main/kotlin/com/d4rk/cleaner/ui/components/layouts/NonLazyGrid.kt: -------------------------------------------------------------------------------- 1 | package com.d4rk.cleaner.ui.components.layouts 2 | 3 | import androidx.compose.foundation.layout.Box 4 | import androidx.compose.foundation.layout.Column 5 | import androidx.compose.foundation.layout.Row 6 | import androidx.compose.foundation.layout.Spacer 7 | import androidx.compose.foundation.layout.aspectRatio 8 | import androidx.compose.foundation.layout.padding 9 | import androidx.compose.runtime.Composable 10 | import androidx.compose.ui.Modifier 11 | import androidx.compose.ui.unit.dp 12 | 13 | @Composable 14 | fun NonLazyGrid( 15 | columns : Int , itemCount : Int , modifier : Modifier = Modifier , content : @Composable (Int) -> Unit 16 | ) { 17 | Column(modifier = modifier) { 18 | val rows : Int = (itemCount + columns - 1) / columns 19 | 20 | (0 until rows).forEach { row -> 21 | Row { 22 | (0 until columns).forEachIndexed { col , _ -> 23 | val index : Int = row * columns + col 24 | if (index < itemCount) { 25 | Box( 26 | modifier = Modifier 27 | .weight(weight = 1f) 28 | .padding(all = 8.dp) 29 | .aspectRatio(ratio = 1f) 30 | ) { 31 | content(index) 32 | } 33 | } 34 | else { 35 | Spacer(modifier = Modifier.weight(weight = 1f)) 36 | } 37 | } 38 | } 39 | } 40 | } 41 | } -------------------------------------------------------------------------------- /app/src/main/kotlin/com/d4rk/cleaner/ui/components/modifiers/ViewPagerModifiers.kt: -------------------------------------------------------------------------------- 1 | package com.d4rk.cleaner.ui.components.modifiers 2 | 3 | import androidx.compose.foundation.pager.PagerState 4 | import androidx.compose.runtime.LaunchedEffect 5 | import androidx.compose.runtime.getValue 6 | import androidx.compose.runtime.mutableStateOf 7 | import androidx.compose.runtime.remember 8 | import androidx.compose.runtime.setValue 9 | import androidx.compose.ui.Modifier 10 | import androidx.compose.ui.composed 11 | import androidx.compose.ui.hapticfeedback.HapticFeedback 12 | import androidx.compose.ui.hapticfeedback.HapticFeedbackType 13 | import androidx.compose.ui.platform.LocalHapticFeedback 14 | 15 | fun Modifier.hapticPagerSwipe(pagerState : PagerState) : Modifier = composed { 16 | val haptic : HapticFeedback = LocalHapticFeedback.current 17 | var hasVibrated : Boolean by remember { mutableStateOf(value = false) } 18 | 19 | LaunchedEffect(key1 = pagerState.isScrollInProgress) { 20 | if (pagerState.isScrollInProgress && ! hasVibrated) { 21 | haptic.performHapticFeedback(HapticFeedbackType.LongPress) 22 | hasVibrated = true 23 | } 24 | else if (! pagerState.isScrollInProgress) { 25 | hasVibrated = false 26 | } 27 | } 28 | 29 | return@composed this 30 | } -------------------------------------------------------------------------------- /app/src/main/kotlin/com/d4rk/cleaner/ui/components/navigation/BottomNavigationBar.kt: -------------------------------------------------------------------------------- 1 | package com.d4rk.cleaner.ui.components.navigation 2 | 3 | import android.view.SoundEffectConstants 4 | import android.view.View 5 | import androidx.compose.foundation.basicMarquee 6 | import androidx.compose.foundation.layout.Column 7 | import androidx.compose.material3.Icon 8 | import androidx.compose.material3.NavigationBar 9 | import androidx.compose.material3.NavigationBarItem 10 | import androidx.compose.material3.Text 11 | import androidx.compose.runtime.Composable 12 | import androidx.compose.runtime.collectAsState 13 | import androidx.compose.runtime.getValue 14 | import androidx.compose.ui.Modifier 15 | import androidx.compose.ui.graphics.vector.ImageVector 16 | import androidx.compose.ui.res.stringResource 17 | import androidx.compose.ui.text.style.TextOverflow 18 | import androidx.navigation.NavBackStackEntry 19 | import androidx.navigation.NavController 20 | import androidx.navigation.compose.currentBackStackEntryAsState 21 | import com.d4rk.android.libs.apptoolkit.ui.components.modifiers.bounceClick 22 | import com.d4rk.cleaner.data.datastore.DataStore 23 | import com.d4rk.cleaner.data.model.ui.navigation.BottomNavigationScreen 24 | import com.d4rk.cleaner.data.model.ui.screens.UiMainScreen 25 | import com.d4rk.cleaner.ui.components.ads.AdBanner 26 | import com.d4rk.cleaner.ui.screens.main.MainViewModel 27 | import com.google.android.gms.ads.AdSize 28 | 29 | @Composable 30 | fun BottomNavigationBar( 31 | navController : NavController , 32 | viewModel : MainViewModel , 33 | dataStore : DataStore , 34 | view : View , 35 | ) { 36 | val uiState : UiMainScreen by viewModel.uiState.collectAsState() 37 | val bottomBarItems : List = uiState.bottomNavigationItems 38 | val showLabels : Boolean = dataStore.getShowBottomBarLabels().collectAsState(initial = true).value 39 | 40 | Column { 41 | AdBanner(adSize = AdSize.FULL_BANNER) 42 | NavigationBar { 43 | val navBackStackEntry : NavBackStackEntry? by navController.currentBackStackEntryAsState() 44 | val currentRoute : String? = navBackStackEntry?.destination?.route 45 | bottomBarItems.forEach { screen -> 46 | NavigationBarItem(icon = { 47 | val iconResource : ImageVector = 48 | if (currentRoute == screen.route) screen.selectedIcon else screen.icon 49 | Icon( 50 | imageVector = iconResource , 51 | modifier = Modifier.bounceClick() , 52 | contentDescription = null 53 | ) 54 | } , label = { 55 | if (showLabels) Text( 56 | text = stringResource(id = screen.title) , 57 | overflow = TextOverflow.Ellipsis , 58 | modifier = Modifier.basicMarquee() 59 | ) 60 | } , selected = currentRoute == screen.route , onClick = { 61 | view.playSoundEffect(SoundEffectConstants.CLICK) 62 | if (currentRoute != screen.route) { 63 | navController.navigate(route = screen.route) { 64 | popUpTo(id = navController.graph.startDestinationId) { 65 | saveState = false 66 | } 67 | launchSingleTop = true 68 | } 69 | viewModel.updateBottomNavigationScreen(newScreen = screen) 70 | } 71 | }) 72 | } 73 | } 74 | } 75 | } -------------------------------------------------------------------------------- /app/src/main/kotlin/com/d4rk/cleaner/ui/components/navigation/LeftNavigationRail.kt: -------------------------------------------------------------------------------- 1 | package com.d4rk.cleaner.ui.components.navigation 2 | 3 | import androidx.compose.animation.core.animateDpAsState 4 | import androidx.compose.animation.core.tween 5 | import androidx.compose.foundation.layout.Box 6 | import androidx.compose.foundation.layout.PaddingValues 7 | import androidx.compose.foundation.layout.Row 8 | import androidx.compose.foundation.layout.Spacer 9 | import androidx.compose.foundation.layout.padding 10 | import androidx.compose.foundation.layout.width 11 | import androidx.compose.material3.Icon 12 | import androidx.compose.material3.NavigationRail 13 | import androidx.compose.material3.NavigationRailItem 14 | import androidx.compose.material3.Text 15 | import androidx.compose.runtime.Composable 16 | import androidx.compose.runtime.collectAsState 17 | import androidx.compose.runtime.getValue 18 | import androidx.compose.ui.Modifier 19 | import androidx.compose.ui.res.stringResource 20 | import androidx.compose.ui.unit.Dp 21 | import androidx.compose.ui.unit.dp 22 | import androidx.navigation.NavBackStackEntry 23 | import androidx.navigation.compose.currentBackStackEntryAsState 24 | import com.d4rk.android.libs.apptoolkit.data.model.ui.navigation.NavigationDrawerItem 25 | import com.d4rk.cleaner.data.model.ui.navigation.BottomNavigationScreen 26 | import com.d4rk.cleaner.data.model.ui.screens.MainScreenState 27 | import com.d4rk.cleaner.data.model.ui.screens.UiMainScreen 28 | import kotlinx.coroutines.CoroutineScope 29 | 30 | @Composable 31 | fun LeftNavigationRail( 32 | mainScreenState : MainScreenState , 33 | paddingValues : PaddingValues , 34 | coroutineScope : CoroutineScope , 35 | isRailExpanded : Boolean , 36 | ) { 37 | val uiState : UiMainScreen by mainScreenState.viewModel.uiState.collectAsState() 38 | 39 | val bottomBarItems : List = uiState.bottomNavigationItems 40 | val drawerItems : List = uiState.navigationDrawerItems 41 | 42 | val navBackStackEntry : NavBackStackEntry? by mainScreenState.navHostController.currentBackStackEntryAsState() 43 | val currentRoute : String? = navBackStackEntry?.destination?.route 44 | 45 | val railWidth : Dp by animateDpAsState( 46 | targetValue = if (isRailExpanded) 200.dp else 72.dp , animationSpec = tween(durationMillis = 300) 47 | ) 48 | 49 | Row(modifier = Modifier.padding(top = paddingValues.calculateTopPadding())) { 50 | NavigationRail( 51 | modifier = Modifier.width(width = railWidth) 52 | ) { 53 | bottomBarItems.forEach { screen -> 54 | NavigationRailItem(selected = currentRoute == screen.route , onClick = { 55 | mainScreenState.navHostController.navigate(screen.route) { 56 | popUpTo(mainScreenState.navHostController.graph.startDestinationId) { 57 | saveState = true 58 | } 59 | launchSingleTop = true 60 | } 61 | } , icon = { 62 | Icon( 63 | imageVector = if (currentRoute == screen.route) screen.selectedIcon else screen.icon , contentDescription = stringResource(id = screen.title) 64 | ) 65 | } , label = if (isRailExpanded) { 66 | { Text(text = stringResource(id = screen.title)) } 67 | } 68 | else null) 69 | } 70 | 71 | Spacer(modifier = Modifier.weight(weight = 1f)) 72 | 73 | drawerItems.forEach { item -> 74 | NavigationRailItem(selected = false , onClick = { 75 | 76 | handleNavigationItemClick( 77 | context = mainScreenState.context , item = item , drawerState = mainScreenState.drawerState , coroutineScope = coroutineScope 78 | ) 79 | } , icon = { 80 | Icon( 81 | imageVector = item.selectedIcon , contentDescription = stringResource(id = item.title) 82 | ) 83 | } , label = if (isRailExpanded) { 84 | { Text(text = stringResource(id = item.title)) } 85 | } 86 | else null) 87 | } 88 | } 89 | 90 | Box(modifier = Modifier.weight(weight = 1f)) { 91 | NavigationHost( 92 | navHostController = mainScreenState.navHostController , dataStore = mainScreenState.dataStore , paddingValues = paddingValues 93 | ) 94 | } 95 | } 96 | } -------------------------------------------------------------------------------- /app/src/main/kotlin/com/d4rk/cleaner/ui/components/navigation/NavigationDrawer.kt: -------------------------------------------------------------------------------- 1 | package com.d4rk.cleaner.ui.components.navigation 2 | 3 | import android.content.Context 4 | import androidx.compose.foundation.layout.padding 5 | import androidx.compose.material3.DrawerState 6 | import androidx.compose.material3.HorizontalDivider 7 | import androidx.compose.material3.Icon 8 | import androidx.compose.material3.ModalDrawerSheet 9 | import androidx.compose.material3.ModalNavigationDrawer 10 | import androidx.compose.material3.NavigationDrawerItem 11 | import androidx.compose.material3.NavigationDrawerItemDefaults 12 | import androidx.compose.material3.Text 13 | import androidx.compose.runtime.Composable 14 | import androidx.compose.runtime.collectAsState 15 | import androidx.compose.runtime.getValue 16 | import androidx.compose.runtime.rememberCoroutineScope 17 | import androidx.compose.ui.Modifier 18 | import androidx.compose.ui.res.stringResource 19 | import androidx.compose.ui.unit.dp 20 | import com.d4rk.android.libs.apptoolkit.ui.components.modifiers.bounceClick 21 | import com.d4rk.android.libs.apptoolkit.ui.components.modifiers.hapticDrawerSwipe 22 | import com.d4rk.android.libs.apptoolkit.ui.components.spacers.LargeVerticalSpacer 23 | import com.d4rk.cleaner.R 24 | import com.d4rk.cleaner.data.model.ui.screens.MainScreenState 25 | import com.d4rk.cleaner.ui.screens.main.MainScaffoldContent 26 | import kotlinx.coroutines.CoroutineScope 27 | 28 | @Composable 29 | fun NavigationDrawer( 30 | mainScreenState : MainScreenState 31 | ) { 32 | val uiState by mainScreenState.viewModel.uiState.collectAsState() 33 | val drawerItems = uiState.navigationDrawerItems 34 | val coroutineScope : CoroutineScope = rememberCoroutineScope() 35 | 36 | ModalNavigationDrawer(modifier = Modifier.hapticDrawerSwipe(drawerState = mainScreenState.drawerState) , drawerState = mainScreenState.drawerState , drawerContent = { 37 | ModalDrawerSheet { 38 | LargeVerticalSpacer() 39 | drawerItems.forEach { item -> 40 | NavigationDrawerItemContent( 41 | item = item , coroutineScope = coroutineScope , drawerState = mainScreenState.drawerState , context = mainScreenState.context 42 | ) 43 | } 44 | } 45 | }) { 46 | MainScaffoldContent( 47 | mainScreenState = mainScreenState , coroutineScope = coroutineScope 48 | ) 49 | } 50 | } 51 | 52 | @Composable 53 | private fun NavigationDrawerItemContent( 54 | item : com.d4rk.android.libs.apptoolkit.data.model.ui.navigation.NavigationDrawerItem , coroutineScope : CoroutineScope , drawerState : DrawerState , context : Context 55 | ) { 56 | val title = stringResource(id = item.title) 57 | NavigationDrawerItem(label = { Text(text = title) } , selected = false , onClick = { 58 | handleNavigationItemClick( 59 | context = context , item = item , drawerState = drawerState , coroutineScope = coroutineScope 60 | ) 61 | } , icon = { 62 | Icon(item.selectedIcon , contentDescription = title) 63 | } , badge = { 64 | if (item.badgeText.isNotBlank()) { 65 | Text(text = item.badgeText) 66 | } 67 | } , modifier = Modifier 68 | .padding(paddingValues = NavigationDrawerItemDefaults.ItemPadding) 69 | .bounceClick()) 70 | 71 | if (item.title == R.string.trash) { 72 | HorizontalDivider(modifier = Modifier.padding(all = 8.dp)) 73 | } 74 | } -------------------------------------------------------------------------------- /app/src/main/kotlin/com/d4rk/cleaner/ui/components/navigation/NavigationHost.kt: -------------------------------------------------------------------------------- 1 | package com.d4rk.cleaner.ui.components.navigation 2 | 3 | import android.content.Context 4 | import androidx.compose.foundation.layout.Box 5 | import androidx.compose.foundation.layout.PaddingValues 6 | import androidx.compose.foundation.layout.padding 7 | import androidx.compose.material3.DrawerState 8 | import androidx.compose.runtime.Composable 9 | import androidx.compose.runtime.collectAsState 10 | import androidx.compose.ui.Modifier 11 | import androidx.compose.ui.platform.LocalContext 12 | import androidx.navigation.NavHostController 13 | import androidx.navigation.compose.NavHost 14 | import androidx.navigation.compose.composable 15 | import com.d4rk.android.libs.apptoolkit.data.model.ui.navigation.NavigationDrawerItem 16 | import com.d4rk.android.libs.apptoolkit.utils.helpers.IntentsHelper 17 | import com.d4rk.android.libs.apptoolkit.utils.helpers.ScreenHelper 18 | import com.d4rk.cleaner.R 19 | import com.d4rk.cleaner.utils.constants.ui.bottombar.BottomBarRoutes 20 | import com.d4rk.cleaner.data.datastore.DataStore 21 | import com.d4rk.cleaner.data.model.ui.navigation.BottomNavigationScreen 22 | import com.d4rk.cleaner.ui.screens.appmanager.AppManagerScreen 23 | import com.d4rk.cleaner.ui.screens.help.HelpActivity 24 | import com.d4rk.cleaner.ui.screens.home.HomeScreen 25 | import com.d4rk.cleaner.ui.screens.imageoptimizer.imagepicker.ImagePickerActivity 26 | import com.d4rk.cleaner.ui.screens.memory.MemoryManagerComposable 27 | import com.d4rk.cleaner.ui.screens.settings.SettingsActivity 28 | import com.d4rk.cleaner.ui.screens.trash.TrashActivity 29 | import kotlinx.coroutines.CoroutineScope 30 | import kotlinx.coroutines.launch 31 | 32 | @Composable 33 | fun NavigationHost( 34 | navHostController : NavHostController , dataStore : DataStore , paddingValues : PaddingValues 35 | ) { 36 | val context : Context = LocalContext.current 37 | val startupPage : String = dataStore.getStartupPage().collectAsState(initial = BottomBarRoutes.HOME).value 38 | val isTabletOrLandscape : Boolean = ScreenHelper.isLandscapeOrTablet(context = context) 39 | 40 | val finalPaddingValues : PaddingValues = if (isTabletOrLandscape) { 41 | PaddingValues(bottom = paddingValues.calculateBottomPadding()) 42 | } 43 | else { 44 | paddingValues 45 | } 46 | 47 | NavHost(navController = navHostController , startDestination = startupPage) { 48 | composable(route = BottomNavigationScreen.Home.route) { 49 | Box(modifier = Modifier.padding(paddingValues = finalPaddingValues)) { 50 | HomeScreen() 51 | } 52 | } 53 | 54 | composable(route = BottomNavigationScreen.AppManager.route) { 55 | Box(modifier = Modifier.padding(paddingValues = finalPaddingValues)) { 56 | AppManagerScreen() 57 | } 58 | } 59 | 60 | composable(route = BottomNavigationScreen.MemoryManager.route) { 61 | Box(modifier = Modifier.padding(paddingValues = finalPaddingValues)) { 62 | MemoryManagerComposable() 63 | } 64 | } 65 | } 66 | } 67 | 68 | fun handleNavigationItemClick( 69 | context : Context , item : NavigationDrawerItem , drawerState : DrawerState , coroutineScope : CoroutineScope 70 | ) { 71 | when (item.title) { 72 | R.string.image_optimizer -> IntentsHelper.openActivity( 73 | context = context , activityClass = ImagePickerActivity::class.java 74 | ) 75 | 76 | R.string.trash -> IntentsHelper.openActivity( 77 | context = context , activityClass = TrashActivity::class.java 78 | ) 79 | 80 | com.d4rk.android.libs.apptoolkit.R.string.settings -> IntentsHelper.openActivity( 81 | context = context , activityClass = SettingsActivity::class.java 82 | ) 83 | 84 | com.d4rk.android.libs.apptoolkit.R.string.help_and_feedback -> IntentsHelper.openActivity( 85 | context = context , activityClass = HelpActivity::class.java 86 | ) 87 | 88 | com.d4rk.android.libs.apptoolkit.R.string.updates -> IntentsHelper.openUrl( 89 | context = context , url = "https://github.com/D4rK7355608/${context.packageName}/blob/master/CHANGELOG.md" 90 | ) 91 | 92 | com.d4rk.android.libs.apptoolkit.R.string.share -> IntentsHelper.shareApp( 93 | context = context , shareMessageFormat = com.d4rk.android.libs.apptoolkit.R.string.summary_share_message 94 | ) 95 | } 96 | coroutineScope.launch { drawerState.close() } 97 | } -------------------------------------------------------------------------------- /app/src/main/kotlin/com/d4rk/cleaner/ui/components/navigation/TopAppBar.kt: -------------------------------------------------------------------------------- 1 | package com.d4rk.cleaner.ui.components.navigation 2 | 3 | import android.content.Context 4 | import androidx.compose.material.icons.Icons 5 | import androidx.compose.material.icons.outlined.VolunteerActivism 6 | import androidx.compose.material3.ExperimentalMaterial3Api 7 | import androidx.compose.material3.Text 8 | import androidx.compose.material3.TopAppBar 9 | import androidx.compose.runtime.Composable 10 | import androidx.compose.ui.graphics.vector.ImageVector 11 | import androidx.compose.ui.res.stringResource 12 | import com.d4rk.android.libs.apptoolkit.ui.components.buttons.AnimatedButtonDirection 13 | import com.d4rk.android.libs.apptoolkit.utils.helpers.IntentsHelper 14 | import com.d4rk.cleaner.R 15 | import com.d4rk.cleaner.ui.screens.support.SupportActivity 16 | 17 | @OptIn(ExperimentalMaterial3Api::class) 18 | @Composable 19 | fun TopAppBarMain(context : Context , navigationIcon : ImageVector , onNavigationIconClick : () -> Unit) { 20 | TopAppBar(title = { Text(text = stringResource(id = R.string.app_name)) } , navigationIcon = { 21 | AnimatedButtonDirection( 22 | icon = navigationIcon , 23 | contentDescription = stringResource(id = com.d4rk.android.libs.apptoolkit.R.string.go_back) , 24 | onClick = { 25 | onNavigationIconClick() 26 | } , 27 | ) 28 | } , actions = { 29 | AnimatedButtonDirection( 30 | fromRight = true , 31 | icon = Icons.Outlined.VolunteerActivism , 32 | contentDescription = stringResource(id = com.d4rk.android.libs.apptoolkit.R.string.go_back) , 33 | onClick = { 34 | IntentsHelper.openActivity(context , SupportActivity::class.java) 35 | } , 36 | ) 37 | }) 38 | } -------------------------------------------------------------------------------- /app/src/main/kotlin/com/d4rk/cleaner/ui/screens/analyze/components/DateHeader.kt: -------------------------------------------------------------------------------- 1 | package com.d4rk.cleaner.ui.screens.analyze.components 2 | 3 | import android.content.Context 4 | import android.view.SoundEffectConstants 5 | import android.view.View 6 | import androidx.compose.foundation.layout.Arrangement 7 | import androidx.compose.foundation.layout.Row 8 | import androidx.compose.foundation.layout.fillMaxWidth 9 | import androidx.compose.foundation.layout.padding 10 | import androidx.compose.material3.Checkbox 11 | import androidx.compose.material3.Text 12 | import androidx.compose.runtime.Composable 13 | import androidx.compose.ui.Alignment 14 | import androidx.compose.ui.Modifier 15 | import androidx.compose.ui.platform.LocalContext 16 | import androidx.compose.ui.unit.dp 17 | import com.d4rk.android.libs.apptoolkit.ui.components.modifiers.bounceClick 18 | import com.d4rk.cleaner.utils.helpers.TimeHelper 19 | import java.io.File 20 | import java.util.Date 21 | 22 | @Composable 23 | fun DateHeader( 24 | files : List , 25 | fileSelectionStates : Map , 26 | onFileSelectionChange : (File , Boolean) -> Unit , 27 | view : View , 28 | ) { 29 | val context : Context = LocalContext.current 30 | Row( 31 | modifier = Modifier 32 | .fillMaxWidth() 33 | .padding(horizontal = 8.dp , vertical = 4.dp) , verticalAlignment = Alignment.CenterVertically , horizontalArrangement = Arrangement.SpaceBetween 34 | ) { 35 | Text( 36 | modifier = Modifier.padding(start = 8.dp) , text = TimeHelper.formatDate(date = Date(files[0].lastModified()) , context = context) 37 | ) 38 | val allFilesForDateSelected : Boolean = files.all { fileSelectionStates[it] == true } 39 | Checkbox(modifier = Modifier.bounceClick() , checked = allFilesForDateSelected , onCheckedChange = { isChecked -> 40 | view.playSoundEffect(SoundEffectConstants.CLICK) 41 | files.forEach { file -> 42 | onFileSelectionChange(file , isChecked) 43 | } 44 | }) 45 | } 46 | } -------------------------------------------------------------------------------- /app/src/main/kotlin/com/d4rk/cleaner/ui/screens/analyze/components/DeleteOrTrashConfirmation.kt: -------------------------------------------------------------------------------- 1 | package com.d4rk.cleaner.ui.screens.analyze.components 2 | 3 | import androidx.compose.runtime.Composable 4 | import androidx.compose.ui.res.stringResource 5 | import com.d4rk.cleaner.R 6 | import com.d4rk.cleaner.data.model.ui.screens.UiHomeModel 7 | import com.d4rk.cleaner.ui.components.dialogs.ConfirmationAlertDialog 8 | import com.d4rk.cleaner.ui.screens.home.HomeViewModel 9 | 10 | @Composable 11 | fun DeleteOrTrashConfirmation(data : UiHomeModel , viewModel : HomeViewModel) { 12 | val isDeleteDialog : Boolean = data.analyzeState.isDeleteForeverConfirmationDialogVisible 13 | 14 | val titleRes : Int = if (isDeleteDialog) R.string.delete_forever_title else R.string.move_to_trash_title 15 | val messageRes : Int = if (isDeleteDialog) R.string.delete_forever_message else R.string.move_to_trash_message 16 | 17 | ConfirmationAlertDialog(confirmationTitle = stringResource(id = titleRes) , 18 | confirmationMessage = stringResource(id = messageRes) , 19 | confirmationConfirmButtonText = stringResource(id = android.R.string.ok) , 20 | confirmationDismissButtonText = stringResource(id = android.R.string.cancel) , 21 | onConfirm = { 22 | if (isDeleteDialog) { 23 | viewModel.clean() 24 | viewModel.setDeleteForeverConfirmationDialogVisibility(false) 25 | } 26 | else { 27 | viewModel.moveToTrash() 28 | viewModel.setMoveToTrashConfirmationDialogVisibility(false) 29 | } 30 | } , 31 | onDismiss = { 32 | if (isDeleteDialog) { 33 | viewModel.setDeleteForeverConfirmationDialogVisibility(false) 34 | } 35 | else { 36 | viewModel.setMoveToTrashConfirmationDialogVisibility(false) 37 | } 38 | }) 39 | } -------------------------------------------------------------------------------- /app/src/main/kotlin/com/d4rk/cleaner/ui/screens/analyze/components/FilesByDateSection.kt: -------------------------------------------------------------------------------- 1 | package com.d4rk.cleaner.ui.screens.analyze.components 2 | 3 | import android.view.View 4 | import androidx.compose.foundation.layout.fillMaxSize 5 | import androidx.compose.foundation.lazy.LazyColumn 6 | import androidx.compose.runtime.Composable 7 | import androidx.compose.ui.Modifier 8 | import coil3.ImageLoader 9 | import java.io.File 10 | import java.text.SimpleDateFormat 11 | import java.util.Locale 12 | 13 | @Composable 14 | fun FilesByDateSection( 15 | modifier : Modifier , 16 | filesByDate : Map> , 17 | fileSelectionStates : Map , 18 | imageLoader : ImageLoader , 19 | onFileSelectionChange : (File , Boolean) -> Unit , 20 | view : View , 21 | ) { 22 | LazyColumn( 23 | modifier = modifier.fillMaxSize() 24 | ) { 25 | val sortedDates : List = filesByDate.keys.sortedByDescending { dateString -> 26 | SimpleDateFormat("yyyy-MM-dd" , Locale.getDefault()).parse(dateString) 27 | } 28 | 29 | sortedDates.forEach { date -> 30 | val files : List = filesByDate[date] ?: emptyList() 31 | item(key = date) { 32 | DateHeader( 33 | files = files , fileSelectionStates = fileSelectionStates , onFileSelectionChange = onFileSelectionChange , view = view 34 | ) 35 | } 36 | 37 | item(key = "$date-grid") { 38 | FilesGrid( 39 | files = files , 40 | imageLoader = imageLoader , 41 | fileSelectionStates = fileSelectionStates , 42 | onFileSelectionChange = onFileSelectionChange , 43 | view = view , 44 | ) 45 | } 46 | } 47 | } 48 | } -------------------------------------------------------------------------------- /app/src/main/kotlin/com/d4rk/cleaner/ui/screens/analyze/components/FilesGrid.kt: -------------------------------------------------------------------------------- 1 | package com.d4rk.cleaner.ui.screens.analyze.components 2 | 3 | import android.view.View 4 | import androidx.compose.foundation.layout.Box 5 | import androidx.compose.foundation.layout.fillMaxSize 6 | import androidx.compose.foundation.layout.padding 7 | import androidx.compose.runtime.Composable 8 | import androidx.compose.ui.Modifier 9 | import androidx.compose.ui.platform.LocalContext 10 | import androidx.compose.ui.unit.dp 11 | import coil3.ImageLoader 12 | import com.d4rk.android.libs.apptoolkit.utils.helpers.ScreenHelper 13 | import com.d4rk.cleaner.ui.components.layouts.NonLazyGrid 14 | import java.io.File 15 | 16 | @Composable 17 | fun FilesGrid( 18 | files : List , 19 | imageLoader : ImageLoader , 20 | fileSelectionStates : Map , 21 | onFileSelectionChange : (File , Boolean) -> Unit , 22 | view : View , 23 | ) { 24 | val columns : Int = if (ScreenHelper.isTablet(context = LocalContext.current)) 6 else 3 25 | 26 | Box( 27 | modifier = Modifier.fillMaxSize() 28 | ) { 29 | NonLazyGrid( 30 | columns = columns , itemCount = files.size , modifier = Modifier.padding(horizontal = 8.dp) 31 | ) { index -> 32 | val file : File = files[index] 33 | FileCard(file = file , imageLoader = imageLoader , isChecked = fileSelectionStates[file] == true , onCheckedChange = { isChecked -> onFileSelectionChange(file , isChecked) } , view = view , modifier = Modifier) 34 | } 35 | } 36 | } -------------------------------------------------------------------------------- /app/src/main/kotlin/com/d4rk/cleaner/ui/screens/analyze/components/SelectAllComposable.kt: -------------------------------------------------------------------------------- 1 | package com.d4rk.cleaner.ui.screens.analyze.components 2 | 3 | import android.view.SoundEffectConstants 4 | import android.view.View 5 | import androidx.compose.animation.AnimatedContent 6 | import androidx.compose.animation.animateContentSize 7 | import androidx.compose.foundation.interaction.MutableInteractionSource 8 | import androidx.compose.foundation.layout.Arrangement 9 | import androidx.compose.foundation.layout.Row 10 | import androidx.compose.foundation.layout.fillMaxWidth 11 | import androidx.compose.foundation.layout.size 12 | import androidx.compose.material.icons.Icons 13 | import androidx.compose.material.icons.filled.Check 14 | import androidx.compose.material3.FilterChip 15 | import androidx.compose.material3.Icon 16 | import androidx.compose.material3.Text 17 | import androidx.compose.runtime.Composable 18 | import androidx.compose.runtime.remember 19 | import androidx.compose.ui.Alignment 20 | import androidx.compose.ui.Modifier 21 | import androidx.compose.ui.res.stringResource 22 | import androidx.compose.ui.unit.dp 23 | import com.d4rk.android.libs.apptoolkit.ui.components.modifiers.bounceClick 24 | import com.d4rk.cleaner.R 25 | 26 | /** 27 | * Composable function for selecting or deselecting all items. 28 | * 29 | * This composable displays a filter chip labeled "Select All". When tapped, it toggles the 30 | * selection state and invokes the `onCheckedChange` callback. 31 | * 32 | * @param checked A boolean value indicating whether all items are currently selected. 33 | * @param onCheckedChange A callback function that is invoked when the user taps the chip to change the selection state. 34 | */ 35 | @Composable 36 | fun SelectAllComposable( 37 | selected : Boolean , view : View , onClickSelectAll : () -> Unit 38 | ) { 39 | 40 | Row( 41 | modifier = Modifier 42 | .fillMaxWidth() 43 | .animateContentSize() , verticalAlignment = Alignment.CenterVertically , horizontalArrangement = Arrangement.End 44 | ) { 45 | val interactionSource : MutableInteractionSource = remember { MutableInteractionSource() } 46 | FilterChip( 47 | modifier = Modifier.bounceClick() , 48 | selected = selected , 49 | onClick = { 50 | onClickSelectAll() 51 | view.playSoundEffect(SoundEffectConstants.CLICK) 52 | } , 53 | label = { Text(text = stringResource(id = R.string.select_all)) } , 54 | leadingIcon = { 55 | AnimatedContent( 56 | targetState = selected , label = "Checkmark Animation" 57 | ) { targetChecked -> 58 | if (targetChecked) { 59 | Icon( 60 | imageVector = Icons.Filled.Check , contentDescription = null , modifier = Modifier.size(size = 18.dp) 61 | ) 62 | } 63 | } 64 | } , 65 | interactionSource = interactionSource , 66 | ) 67 | } 68 | } -------------------------------------------------------------------------------- /app/src/main/kotlin/com/d4rk/cleaner/ui/screens/analyze/components/StatusRowSelectAll.kt: -------------------------------------------------------------------------------- 1 | package com.d4rk.cleaner.ui.screens.analyze.components 2 | 3 | import android.view.View 4 | import androidx.compose.animation.animateColorAsState 5 | import androidx.compose.animation.animateContentSize 6 | import androidx.compose.animation.core.tween 7 | import androidx.compose.foundation.layout.Arrangement 8 | import androidx.compose.foundation.layout.Row 9 | import androidx.compose.foundation.layout.fillMaxWidth 10 | import androidx.compose.material3.MaterialTheme 11 | import androidx.compose.material3.Text 12 | import androidx.compose.runtime.Composable 13 | import androidx.compose.runtime.getValue 14 | import androidx.compose.ui.Alignment 15 | import androidx.compose.ui.Modifier 16 | import androidx.compose.ui.graphics.Color 17 | import androidx.compose.ui.res.pluralStringResource 18 | import androidx.compose.ui.res.stringResource 19 | import com.d4rk.cleaner.R 20 | import com.d4rk.cleaner.data.model.ui.screens.UiHomeModel 21 | 22 | @Composable 23 | fun StatusRowSelectAll(data : UiHomeModel , view : View, onClickSelectAll : () -> Unit) { 24 | Row( 25 | modifier = Modifier.fillMaxWidth() , 26 | verticalAlignment = Alignment.CenterVertically , 27 | horizontalArrangement = Arrangement.SpaceBetween , 28 | ) { 29 | val statusText : String = if (data.analyzeState.selectedFilesCount > 0) { 30 | pluralStringResource( 31 | id = R.plurals.status_selected_files , count = data.analyzeState.selectedFilesCount , data.analyzeState.selectedFilesCount 32 | ) 33 | } 34 | else { 35 | stringResource(id = R.string.status_no_files_selected) 36 | } 37 | val statusColor : Color by animateColorAsState( 38 | targetValue = if (data.analyzeState.selectedFilesCount > 0) { 39 | MaterialTheme.colorScheme.primary 40 | } 41 | else { 42 | MaterialTheme.colorScheme.secondary 43 | } , animationSpec = tween() , label = "Selected Files Status Color Animation" 44 | ) 45 | 46 | Text( 47 | text = statusText , color = statusColor , modifier = Modifier.animateContentSize() 48 | ) 49 | SelectAllComposable( 50 | selected = data.analyzeState.areAllFilesSelected , view = view , onClickSelectAll = { 51 | onClickSelectAll() 52 | } 53 | ) 54 | } 55 | } -------------------------------------------------------------------------------- /app/src/main/kotlin/com/d4rk/cleaner/ui/screens/appmanager/repository/AppManagerRepository.kt: -------------------------------------------------------------------------------- 1 | package com.d4rk.cleaner.ui.screens.appmanager.repository 2 | 3 | import android.app.Application 4 | import android.content.Intent 5 | import android.content.pm.ApplicationInfo 6 | import com.d4rk.cleaner.R 7 | import com.d4rk.cleaner.data.model.ui.appmanager.ui.ApkInfo 8 | import kotlinx.coroutines.Dispatchers 9 | import kotlinx.coroutines.withContext 10 | 11 | class AppManagerRepository(application : Application) : 12 | AppManagerRepositoryImplementation(application) { 13 | 14 | suspend fun getInstalledAppsRepository(onSuccess : (List) -> Unit) { 15 | withContext(Dispatchers.IO) { 16 | val installedApps : List = getInstalledAppsImplementation() 17 | withContext(Dispatchers.Main) { 18 | onSuccess(installedApps) 19 | } 20 | } 21 | } 22 | 23 | suspend fun getApkFilesFromStorageRepository(onSuccess : (List) -> Unit) { 24 | withContext(Dispatchers.IO) { 25 | val apkFiles : List = getApkFilesFromStorageImplementation() 26 | withContext(Dispatchers.Main) { 27 | onSuccess(apkFiles) 28 | } 29 | } 30 | } 31 | 32 | suspend fun installApkRepository(apkPath : String , onSuccess : () -> Unit) { 33 | withContext(Dispatchers.IO) { 34 | installApkImplementation(apkPath) 35 | withContext(Dispatchers.Main) { 36 | onSuccess() 37 | } 38 | } 39 | } 40 | 41 | suspend fun shareApkRepository(apkPath : String , onSuccess : () -> Unit) { 42 | withContext(Dispatchers.IO) { 43 | val shareIntent : Intent = prepareShareIntent(apkPath) 44 | withContext(Dispatchers.Main) { 45 | val chooserIntent : Intent = Intent.createChooser( 46 | shareIntent , application.getString(R.string.share_apk) 47 | ) 48 | chooserIntent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK) 49 | application.startActivity(chooserIntent) 50 | onSuccess() 51 | } 52 | } 53 | } 54 | 55 | suspend fun shareAppRepository(packageName : String , onSuccess : () -> Unit) { 56 | withContext(Dispatchers.IO) { 57 | val shareIntent : Intent = shareAppImplementation(packageName) 58 | withContext(Dispatchers.Main) { 59 | val chooserIntent : Intent = Intent.createChooser( 60 | shareIntent , application.getString(com.d4rk.android.libs.apptoolkit.R.string.share) 61 | ) 62 | chooserIntent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK) 63 | application.startActivity(chooserIntent) 64 | onSuccess() 65 | } 66 | } 67 | } 68 | 69 | suspend fun openAppInfoRepository(packageName : String , onSuccess : () -> Unit) { 70 | withContext(Dispatchers.IO) { 71 | openAppInfoImplementation(packageName) 72 | withContext(Dispatchers.Main) { 73 | onSuccess() 74 | } 75 | } 76 | } 77 | 78 | suspend fun uninstallAppRepository(packageName : String , onSuccess : () -> Unit) { 79 | withContext(Dispatchers.IO) { 80 | uninstallAppImplementation(packageName) 81 | withContext(Dispatchers.Main) { 82 | onSuccess() 83 | } 84 | } 85 | } 86 | } -------------------------------------------------------------------------------- /app/src/main/kotlin/com/d4rk/cleaner/ui/screens/help/HelpActivity.kt: -------------------------------------------------------------------------------- 1 | package com.d4rk.cleaner.ui.screens.help 2 | 3 | import android.os.Bundle 4 | import androidx.activity.compose.setContent 5 | import androidx.activity.enableEdgeToEdge 6 | import androidx.activity.viewModels 7 | import androidx.appcompat.app.AppCompatActivity 8 | import androidx.compose.foundation.layout.fillMaxSize 9 | import androidx.compose.material3.MaterialTheme 10 | import androidx.compose.material3.Surface 11 | import androidx.compose.ui.Modifier 12 | import com.d4rk.cleaner.ui.screens.settings.display.theme.style.AppTheme 13 | 14 | class HelpActivity : AppCompatActivity() { 15 | private val viewModel : HelpViewModel by viewModels() 16 | 17 | override fun onCreate(savedInstanceState : Bundle?) { 18 | super.onCreate(savedInstanceState) 19 | enableEdgeToEdge() 20 | setContent { 21 | AppTheme { 22 | Surface( 23 | modifier = Modifier.fillMaxSize() , color = MaterialTheme.colorScheme.background 24 | ) { 25 | HelpScreen(activity = this@HelpActivity , viewModel) 26 | } 27 | } 28 | } 29 | } 30 | } -------------------------------------------------------------------------------- /app/src/main/kotlin/com/d4rk/cleaner/ui/screens/help/HelpViewModel.kt: -------------------------------------------------------------------------------- 1 | package com.d4rk.cleaner.ui.screens.help 2 | 3 | import android.app.Activity 4 | import android.app.Application 5 | import androidx.lifecycle.viewModelScope 6 | import com.d4rk.cleaner.data.model.ui.screens.UiHelpScreen 7 | import com.d4rk.cleaner.ui.screens.help.repository.HelpRepository 8 | import com.d4rk.cleaner.ui.viewmodel.BaseViewModel 9 | import com.google.android.play.core.review.ReviewInfo 10 | import kotlinx.coroutines.delay 11 | import kotlinx.coroutines.flow.MutableStateFlow 12 | import kotlinx.coroutines.flow.StateFlow 13 | import kotlinx.coroutines.launch 14 | 15 | class HelpViewModel(application : Application) : BaseViewModel(application) { 16 | 17 | private val repository : HelpRepository = HelpRepository(application = application) 18 | 19 | private val _uiState : MutableStateFlow = MutableStateFlow(UiHelpScreen()) 20 | val uiState : StateFlow = _uiState 21 | 22 | init { 23 | initializeVisibilityStates() 24 | getFAQs() 25 | requestReviewFlow() 26 | } 27 | 28 | private fun initializeVisibilityStates() { 29 | viewModelScope.launch(context = coroutineExceptionHandler) { 30 | delay(timeMillis = 100L) 31 | showFab() 32 | } 33 | } 34 | 35 | private fun getFAQs() { 36 | viewModelScope.launch(context = coroutineExceptionHandler) { 37 | repository.getFAQsRepository { faqList -> 38 | _uiState.value = _uiState.value.copy(questions = faqList) 39 | } 40 | } 41 | } 42 | 43 | fun requestReviewFlow() { 44 | viewModelScope.launch(context = coroutineExceptionHandler) { 45 | repository.requestReviewFlowRepository(onSuccess = { reviewInfo -> 46 | _uiState.value = _uiState.value.copy(reviewInfo = reviewInfo) 47 | } , onFailure = {}) 48 | } 49 | } 50 | 51 | fun launchReviewFlow(activity : Activity , reviewInfo : ReviewInfo) { 52 | viewModelScope.launch(context = coroutineExceptionHandler) { 53 | repository.launchReviewFlowRepository(activity = activity , reviewInfo = reviewInfo) 54 | } 55 | } 56 | } -------------------------------------------------------------------------------- /app/src/main/kotlin/com/d4rk/cleaner/ui/screens/help/repository/HelpRepository.kt: -------------------------------------------------------------------------------- 1 | package com.d4rk.cleaner.ui.screens.help.repository 2 | 3 | import android.app.Activity 4 | import android.app.Application 5 | import com.d4rk.cleaner.data.model.ui.screens.UiHelpQuestion 6 | import com.google.android.play.core.review.ReviewInfo 7 | import kotlinx.coroutines.Dispatchers 8 | import kotlinx.coroutines.withContext 9 | 10 | class HelpRepository(application : Application) : HelpRepositoryImplementation(application) { 11 | 12 | suspend fun getFAQsRepository(onSuccess : (ArrayList) -> Unit) { 13 | withContext(Dispatchers.IO) { 14 | val questions = getFAQsImplementation().map { (questionRes , summaryRes) -> 15 | UiHelpQuestion( 16 | question = application.getString(questionRes) , answer = application.getString(summaryRes) 17 | ) 18 | }.toCollection(destination = ArrayList()) 19 | 20 | withContext(Dispatchers.Main) { 21 | onSuccess(questions) 22 | } 23 | } 24 | } 25 | 26 | suspend fun requestReviewFlowRepository( 27 | onSuccess : (ReviewInfo) -> Unit , onFailure : () -> Unit 28 | ) { 29 | withContext(Dispatchers.IO) { 30 | requestReviewFlowImplementation(onSuccess = onSuccess , onFailure = onFailure) 31 | } 32 | } 33 | 34 | suspend fun launchReviewFlowRepository(activity : Activity , reviewInfo : ReviewInfo) { 35 | withContext(Dispatchers.IO) { 36 | launchReviewFlowImplementation(activity = activity , reviewInfo = reviewInfo) 37 | } 38 | } 39 | } -------------------------------------------------------------------------------- /app/src/main/kotlin/com/d4rk/cleaner/ui/screens/help/repository/HelpRepositoryImplementation.kt: -------------------------------------------------------------------------------- 1 | package com.d4rk.cleaner.ui.screens.help.repository 2 | 3 | import android.app.Activity 4 | import android.app.Application 5 | import com.d4rk.android.libs.apptoolkit.utils.helpers.IntentsHelper 6 | import com.d4rk.cleaner.R 7 | import com.google.android.gms.tasks.Task 8 | import com.google.android.play.core.review.ReviewInfo 9 | import com.google.android.play.core.review.ReviewManager 10 | import com.google.android.play.core.review.ReviewManagerFactory 11 | 12 | abstract class HelpRepositoryImplementation( 13 | val application : Application 14 | ) { 15 | fun getFAQsImplementation() : List> { 16 | return listOf( 17 | R.string.question_1 to R.string.summary_preference_faq_1 , 18 | R.string.question_2 to R.string.summary_preference_faq_2 , 19 | R.string.question_3 to R.string.summary_preference_faq_3 , 20 | R.string.question_4 to R.string.summary_preference_faq_4 , 21 | R.string.question_5 to R.string.summary_preference_faq_5 , 22 | R.string.question_6 to R.string.summary_preference_faq_6 , 23 | R.string.question_7 to R.string.summary_preference_faq_7 , 24 | R.string.question_8 to R.string.summary_preference_faq_8 , 25 | R.string.question_9 to R.string.summary_preference_faq_9 26 | ) 27 | } 28 | 29 | open suspend fun requestReviewFlowImplementation( 30 | onSuccess : (ReviewInfo) -> Unit , onFailure : () -> Unit 31 | ) { 32 | val reviewManager : ReviewManager = ReviewManagerFactory.create(application) 33 | val request : Task = reviewManager.requestReviewFlow() 34 | val packageName : String = application.packageName 35 | 36 | request.addOnCompleteListener { task -> 37 | if (task.isSuccessful) { 38 | onSuccess(task.result) 39 | } 40 | else { 41 | onFailure() 42 | } 43 | }.addOnFailureListener { 44 | IntentsHelper.openUrl( 45 | context = application , url = "https://play.google.com/store/apps/details?id=$packageName&showAllReviews=true" 46 | ) 47 | } 48 | } 49 | 50 | fun launchReviewFlowImplementation(activity : Activity , reviewInfo : ReviewInfo) { 51 | val reviewManager : ReviewManager = ReviewManagerFactory.create(activity) 52 | reviewManager.launchReviewFlow(activity , reviewInfo) 53 | } 54 | } -------------------------------------------------------------------------------- /app/src/main/kotlin/com/d4rk/cleaner/ui/screens/home/repository/HomeRepository.kt: -------------------------------------------------------------------------------- 1 | package com.d4rk.cleaner.ui.screens.home.repository 2 | 3 | import android.app.Application 4 | import android.os.Environment 5 | import com.d4rk.cleaner.data.datastore.DataStore 6 | import com.d4rk.cleaner.data.model.ui.screens.FileTypesData 7 | import com.d4rk.cleaner.data.model.ui.screens.UiHomeModel 8 | import com.d4rk.cleaner.utils.constants.cleaning.ExtensionsConstants 9 | import kotlinx.coroutines.Dispatchers 10 | import kotlinx.coroutines.flow.first 11 | import kotlinx.coroutines.withContext 12 | import java.io.File 13 | 14 | class HomeRepository(dataStore : DataStore , application : Application) : HomeRepositoryImplementation(application , dataStore) { 15 | 16 | suspend fun getPreferences(): Map { 17 | return withContext(context = Dispatchers.IO) { 18 | mapOf( 19 | ExtensionsConstants.GENERIC_EXTENSIONS to dataStore.genericFilter.first(), 20 | ExtensionsConstants.IMAGE_EXTENSIONS to dataStore.deleteImageFiles.first(), 21 | ExtensionsConstants.VIDEO_EXTENSIONS to dataStore.deleteVideoFiles.first(), 22 | ExtensionsConstants.AUDIO_EXTENSIONS to dataStore.deleteAudioFiles.first(), 23 | ExtensionsConstants.OFFICE_EXTENSIONS to dataStore.deleteOfficeFiles.first(), 24 | ExtensionsConstants.ARCHIVE_EXTENSIONS to dataStore.deleteArchives.first(), 25 | ExtensionsConstants.APK_EXTENSIONS to dataStore.deleteApkFiles.first(), 26 | ExtensionsConstants.FONT_EXTENSIONS to dataStore.deleteFontFiles.first(), 27 | ExtensionsConstants.WINDOWS_EXTENSIONS to dataStore.deleteWindowsFiles.first(), 28 | ExtensionsConstants.EMPTY_FOLDERS to dataStore.deleteEmptyFolders.first(), 29 | ExtensionsConstants.OTHER_EXTENSIONS to dataStore.deleteOtherFiles.first() 30 | ) 31 | 32 | } 33 | } 34 | 35 | suspend fun getStorageInfoRepository(onSuccess : (UiHomeModel) -> Unit) { 36 | withContext(context = Dispatchers.IO) { 37 | val storageInfo : UiHomeModel = getStorageInfoImplementation() 38 | withContext(context = Dispatchers.Main) { onSuccess(storageInfo) } 39 | } 40 | } 41 | 42 | suspend fun getFileTypesRepository(onSuccess : (FileTypesData) -> Unit) { 43 | withContext(context = Dispatchers.IO) { 44 | val fileTypesData : FileTypesData = getFileTypesImplementation() 45 | withContext(context = Dispatchers.Main) { onSuccess(fileTypesData) } 46 | } 47 | } 48 | 49 | suspend fun analyzeFiles(onSuccess : (Pair , List>) -> Unit) { 50 | withContext(context = Dispatchers.IO) { 51 | val result = getAllFilesImplementation() 52 | withContext(context = Dispatchers.Main) { onSuccess(result) } 53 | } 54 | } 55 | 56 | suspend fun getTrashFiles() : List { 57 | return withContext(context = Dispatchers.IO) { 58 | val trashDir = File(application.getExternalFilesDir(Environment.DIRECTORY_DOCUMENTS) , "Trash") 59 | if (trashDir.exists()) trashDir.listFiles()?.toList() ?: emptyList() 60 | else emptyList() 61 | } 62 | } 63 | 64 | suspend fun deleteFilesRepository(filesToDelete : Set , onSuccess : () -> Unit) { 65 | withContext(context = Dispatchers.IO) { 66 | deleteFilesImplementation(filesToDelete = filesToDelete) 67 | withContext(context = Dispatchers.Main) { onSuccess() } 68 | } 69 | } 70 | 71 | suspend fun moveToTrashRepository(filesToMove : List , onSuccess : () -> Unit) { 72 | withContext(context = Dispatchers.IO) { 73 | moveToTrashImplementation(filesToMove = filesToMove) 74 | withContext(context = Dispatchers.Main) { onSuccess() } 75 | } 76 | } 77 | 78 | suspend fun restoreFromTrashRepository(filesToRestore : Set , onSuccess : () -> Unit) { 79 | withContext(context = Dispatchers.IO) { 80 | restoreFromTrashImplementation(filesToRestore = filesToRestore) 81 | withContext(context = Dispatchers.Main) { onSuccess() } 82 | } 83 | } 84 | 85 | suspend fun addTrashSize(size : Long) { 86 | dataStore.addTrashSize(size = size) 87 | } 88 | 89 | suspend fun subtractTrashSize(size : Long) { 90 | dataStore.subtractTrashSize(size = size) 91 | } 92 | } -------------------------------------------------------------------------------- /app/src/main/kotlin/com/d4rk/cleaner/ui/screens/imageoptimizer/imageoptimizer/ImageOptimizerActivity.kt: -------------------------------------------------------------------------------- 1 | package com.d4rk.cleaner.ui.screens.imageoptimizer.imageoptimizer 2 | 3 | import android.net.Uri 4 | import android.os.Bundle 5 | import androidx.activity.compose.setContent 6 | import androidx.activity.enableEdgeToEdge 7 | import androidx.activity.viewModels 8 | import androidx.appcompat.app.AppCompatActivity 9 | import androidx.compose.foundation.layout.fillMaxSize 10 | import androidx.compose.material3.MaterialTheme 11 | import androidx.compose.material3.Surface 12 | import androidx.compose.ui.Modifier 13 | import androidx.lifecycle.lifecycleScope 14 | import com.d4rk.cleaner.ui.screens.settings.display.theme.style.AppTheme 15 | import kotlinx.coroutines.launch 16 | 17 | class ImageOptimizerActivity : AppCompatActivity() { 18 | private val viewModel : ImageOptimizerViewModel by viewModels() 19 | 20 | override fun onCreate(savedInstanceState : Bundle?) { 21 | super.onCreate(savedInstanceState) 22 | enableEdgeToEdge() 23 | val selectedImageUriString : String? = intent.getStringExtra("selectedImageUri") 24 | if (! selectedImageUriString.isNullOrEmpty()) { 25 | lifecycleScope.launch { 26 | viewModel.onImageSelected(Uri.parse(selectedImageUriString)) 27 | } 28 | } 29 | 30 | setContent { 31 | AppTheme { 32 | Surface( 33 | modifier = Modifier.fillMaxSize() , color = MaterialTheme.colorScheme.background 34 | ) { 35 | ImageOptimizerScreen(activity = this@ImageOptimizerActivity , viewModel) 36 | } 37 | } 38 | } 39 | } 40 | } -------------------------------------------------------------------------------- /app/src/main/kotlin/com/d4rk/cleaner/ui/screens/imageoptimizer/imageoptimizer/tabs/QuickCompressTab.kt: -------------------------------------------------------------------------------- 1 | package com.d4rk.cleaner.ui.screens.imageoptimizer.imageoptimizer.tabs 2 | 3 | import androidx.compose.foundation.BorderStroke 4 | import androidx.compose.foundation.layout.Column 5 | import androidx.compose.foundation.layout.Row 6 | import androidx.compose.foundation.layout.Spacer 7 | import androidx.compose.foundation.layout.fillMaxWidth 8 | import androidx.compose.foundation.layout.padding 9 | import androidx.compose.foundation.layout.width 10 | import androidx.compose.material3.ButtonDefaults 11 | import androidx.compose.material3.MaterialTheme 12 | import androidx.compose.material3.OutlinedButton 13 | import androidx.compose.material3.Slider 14 | import androidx.compose.material3.Text 15 | import androidx.compose.runtime.Composable 16 | import androidx.compose.runtime.getValue 17 | import androidx.compose.runtime.mutableFloatStateOf 18 | import androidx.compose.runtime.remember 19 | import androidx.compose.runtime.rememberCoroutineScope 20 | import androidx.compose.runtime.setValue 21 | import androidx.compose.ui.Modifier 22 | import androidx.compose.ui.res.stringResource 23 | import androidx.compose.ui.unit.dp 24 | import com.d4rk.android.libs.apptoolkit.ui.components.spacers.LargeVerticalSpacer 25 | import com.d4rk.cleaner.data.model.ui.imageoptimizer.CompressionLevel 26 | import com.d4rk.cleaner.ui.screens.imageoptimizer.imageoptimizer.ImageOptimizerViewModel 27 | import com.d4rk.cleaner.utils.imageoptimizer.getCompressionLevelFromSliderValue 28 | import kotlinx.coroutines.CoroutineScope 29 | import kotlinx.coroutines.launch 30 | 31 | @Composable 32 | fun QuickCompressTab(viewModel : ImageOptimizerViewModel) { 33 | var sliderValue : Float by remember { mutableFloatStateOf(value = 50f) } 34 | val selectedCompression : CompressionLevel = getCompressionLevelFromSliderValue(sliderValue = sliderValue) 35 | val coroutineScope : CoroutineScope = rememberCoroutineScope() 36 | 37 | Column(modifier = Modifier.padding(all = 16.dp)) { 38 | Row(modifier = Modifier.fillMaxWidth()) { 39 | for (compressionLevel in CompressionLevel.entries) { 40 | OutlinedButton(onClick = { 41 | coroutineScope.launch { 42 | sliderValue = compressionLevel.defaultPercentage.toFloat() 43 | viewModel.setQuickCompressValue(sliderValue.toInt()) 44 | } 45 | } , modifier = Modifier.weight(weight = 1f) , border = BorderStroke( 46 | width = 1.dp , color = if (selectedCompression == compressionLevel) MaterialTheme.colorScheme.primary 47 | else MaterialTheme.colorScheme.outline 48 | ) , colors = ButtonDefaults.outlinedButtonColors( 49 | contentColor = if (selectedCompression == compressionLevel) MaterialTheme.colorScheme.primary 50 | else MaterialTheme.colorScheme.onSurface 51 | )) { 52 | Text(text = stringResource(compressionLevel.stringRes)) 53 | } 54 | Spacer(modifier = Modifier.width(8.dp)) 55 | } 56 | } 57 | 58 | LargeVerticalSpacer() 59 | 60 | Slider(value = sliderValue , onValueChange = { newValue -> sliderValue = newValue } , onValueChangeFinished = { 61 | coroutineScope.launch { 62 | viewModel.setQuickCompressValue(value = sliderValue.toInt()) 63 | } 64 | } , valueRange = 0f..100f , steps = 99 65 | ) 66 | } 67 | } -------------------------------------------------------------------------------- /app/src/main/kotlin/com/d4rk/cleaner/ui/screens/imageoptimizer/imagepicker/ImagePickerActivity.kt: -------------------------------------------------------------------------------- 1 | package com.d4rk.cleaner.ui.screens.imageoptimizer.imagepicker 2 | 3 | import android.content.Intent 4 | import android.net.Uri 5 | import android.os.Build 6 | import android.os.Bundle 7 | import androidx.activity.compose.setContent 8 | import androidx.activity.enableEdgeToEdge 9 | import androidx.activity.result.PickVisualMediaRequest 10 | import androidx.activity.result.contract.ActivityResultContracts 11 | import androidx.activity.viewModels 12 | import androidx.appcompat.app.AppCompatActivity 13 | import androidx.compose.foundation.layout.fillMaxSize 14 | import androidx.compose.material3.MaterialTheme 15 | import androidx.compose.material3.Surface 16 | import androidx.compose.ui.Modifier 17 | import com.d4rk.cleaner.ui.screens.settings.display.theme.style.AppTheme 18 | 19 | class ImagePickerActivity : AppCompatActivity() { 20 | private val viewModel: ImagePickerViewModel by viewModels() 21 | 22 | private val pickMediaLauncher = 23 | registerForActivityResult(ActivityResultContracts.PickVisualMedia()) { uri -> 24 | viewModel.setSelectedImageUri(uri) 25 | } 26 | 27 | private val openDocumentLauncher = 28 | registerForActivityResult(ActivityResultContracts.StartActivityForResult()) { result -> 29 | if (result.resultCode == RESULT_OK && result.data != null) { 30 | val selectedImageUri: Uri? = result.data?.data 31 | viewModel.setSelectedImageUri(selectedImageUri) 32 | } 33 | } 34 | 35 | override fun onCreate(savedInstanceState: Bundle?) { 36 | super.onCreate(savedInstanceState) 37 | enableEdgeToEdge() 38 | setContent { 39 | AppTheme { 40 | Surface( 41 | modifier = Modifier.fillMaxSize(), color = MaterialTheme.colorScheme.background 42 | ) { 43 | ImagePickerComposable(activity = this@ImagePickerActivity, viewModel) 44 | } 45 | } 46 | } 47 | } 48 | 49 | fun selectImage() { 50 | if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.TIRAMISU) { 51 | pickMediaLauncher.launch(PickVisualMediaRequest(ActivityResultContracts.PickVisualMedia.ImageOnly)) 52 | } else { 53 | val pickIntent = Intent(Intent.ACTION_OPEN_DOCUMENT).apply { 54 | type = "image/*" 55 | addCategory(Intent.CATEGORY_OPENABLE) 56 | } 57 | 58 | openDocumentLauncher.launch(pickIntent) 59 | 60 | } 61 | } 62 | } -------------------------------------------------------------------------------- /app/src/main/kotlin/com/d4rk/cleaner/ui/screens/imageoptimizer/imagepicker/ImagePickerViewModel.kt: -------------------------------------------------------------------------------- 1 | package com.d4rk.cleaner.ui.screens.imageoptimizer.imagepicker 2 | 3 | import android.app.Application 4 | import android.net.Uri 5 | import androidx.compose.runtime.MutableState 6 | import androidx.compose.runtime.mutableStateOf 7 | import androidx.lifecycle.viewModelScope 8 | import com.d4rk.cleaner.ui.viewmodel.BaseViewModel 9 | import kotlinx.coroutines.delay 10 | import kotlinx.coroutines.launch 11 | 12 | class ImagePickerViewModel(application : Application) : BaseViewModel(application = application) { 13 | private val _selectedImageUri : MutableState = mutableStateOf(value = null) 14 | val selectedImageUri: Uri? get() = _selectedImageUri.value 15 | 16 | init { 17 | initializeVisibilityStates() 18 | } 19 | 20 | private fun initializeVisibilityStates() { 21 | viewModelScope.launch(context = coroutineExceptionHandler) { 22 | delay(timeMillis = 100L) 23 | showFab() 24 | } 25 | } 26 | 27 | fun setSelectedImageUri(uri: Uri?) { 28 | _selectedImageUri.value = uri 29 | } 30 | } -------------------------------------------------------------------------------- /app/src/main/kotlin/com/d4rk/cleaner/ui/screens/main/MainScreen.kt: -------------------------------------------------------------------------------- 1 | package com.d4rk.cleaner.ui.screens.main 2 | 3 | import android.content.Context 4 | import androidx.compose.foundation.layout.fillMaxSize 5 | import androidx.compose.foundation.layout.imePadding 6 | import androidx.compose.material.icons.Icons 7 | import androidx.compose.material.icons.automirrored.outlined.MenuOpen 8 | import androidx.compose.material.icons.filled.Menu 9 | import androidx.compose.material3.DrawerValue 10 | import androidx.compose.material3.Scaffold 11 | import androidx.compose.material3.rememberDrawerState 12 | import androidx.compose.runtime.Composable 13 | import androidx.compose.runtime.getValue 14 | import androidx.compose.runtime.mutableStateOf 15 | import androidx.compose.runtime.remember 16 | import androidx.compose.runtime.rememberCoroutineScope 17 | import androidx.compose.runtime.setValue 18 | import androidx.compose.ui.Modifier 19 | import androidx.compose.ui.platform.LocalContext 20 | import androidx.compose.ui.platform.LocalView 21 | import androidx.navigation.compose.rememberNavController 22 | import com.d4rk.android.libs.apptoolkit.utils.helpers.ScreenHelper 23 | import com.d4rk.cleaner.data.core.AppCoreManager 24 | import com.d4rk.cleaner.data.model.ui.screens.MainScreenState 25 | import com.d4rk.cleaner.ui.components.navigation.BottomNavigationBar 26 | import com.d4rk.cleaner.ui.components.navigation.LeftNavigationRail 27 | import com.d4rk.cleaner.ui.components.navigation.NavigationDrawer 28 | import com.d4rk.cleaner.ui.components.navigation.NavigationHost 29 | import com.d4rk.cleaner.ui.components.navigation.TopAppBarMain 30 | import kotlinx.coroutines.CoroutineScope 31 | import kotlinx.coroutines.launch 32 | 33 | @Composable 34 | fun MainScreen(viewModel : MainViewModel) { 35 | val drawerState = rememberDrawerState(DrawerValue.Closed) 36 | val navController = rememberNavController() 37 | val context = LocalContext.current 38 | val view = LocalView.current 39 | val dataStore = AppCoreManager.dataStore 40 | 41 | val isTabletOrLandscape : Boolean = ScreenHelper.isLandscapeOrTablet(context = context) 42 | 43 | val mainScreenState = remember { 44 | MainScreenState( 45 | context = context , view = view , drawerState = drawerState , navHostController = navController , dataStore = dataStore , viewModel = viewModel 46 | ) 47 | } 48 | 49 | if (isTabletOrLandscape) { 50 | MainScaffoldTabletContent(mainScreenState = mainScreenState) 51 | } 52 | else { 53 | NavigationDrawer( 54 | mainScreenState = mainScreenState 55 | ) 56 | } 57 | } 58 | 59 | @Composable 60 | fun MainScaffoldContent( 61 | mainScreenState : MainScreenState , coroutineScope : CoroutineScope 62 | ) { 63 | Scaffold(modifier = Modifier.imePadding() , topBar = { 64 | TopAppBarMain(context = mainScreenState.context , navigationIcon = if (mainScreenState.drawerState.isOpen) Icons.AutoMirrored.Outlined.MenuOpen else Icons.Default.Menu , onNavigationIconClick = { 65 | coroutineScope.launch { 66 | mainScreenState.drawerState.apply { 67 | if (isClosed) open() else close() 68 | } 69 | } 70 | }) 71 | } , bottomBar = { 72 | BottomNavigationBar( 73 | navController = mainScreenState.navHostController , dataStore = mainScreenState.dataStore , view = mainScreenState.view , viewModel = mainScreenState.viewModel 74 | ) 75 | }) { paddingValues -> 76 | NavigationHost( 77 | navHostController = mainScreenState.navHostController , dataStore = mainScreenState.dataStore , paddingValues = paddingValues 78 | ) 79 | } 80 | } 81 | 82 | @Composable 83 | fun MainScaffoldTabletContent(mainScreenState : MainScreenState) { 84 | var isRailExpanded : Boolean by remember { mutableStateOf(value = false) } 85 | val coroutineScope : CoroutineScope = rememberCoroutineScope() 86 | val context : Context = LocalContext.current 87 | 88 | Scaffold(modifier = Modifier.fillMaxSize() , topBar = { 89 | TopAppBarMain(context = context , navigationIcon = if (isRailExpanded) Icons.AutoMirrored.Outlined.MenuOpen else Icons.Default.Menu , onNavigationIconClick = { 90 | isRailExpanded = ! isRailExpanded 91 | }) 92 | }) { paddingValues -> 93 | LeftNavigationRail( 94 | coroutineScope = coroutineScope , 95 | mainScreenState = mainScreenState , 96 | paddingValues = paddingValues , 97 | isRailExpanded = isRailExpanded , 98 | ) 99 | } 100 | } -------------------------------------------------------------------------------- /app/src/main/kotlin/com/d4rk/cleaner/ui/screens/main/repository/MainRepository.kt: -------------------------------------------------------------------------------- 1 | package com.d4rk.cleaner.ui.screens.main.repository 2 | 3 | import android.app.Application 4 | import android.content.Context 5 | import android.os.Build 6 | import androidx.activity.result.ActivityResultLauncher 7 | import androidx.activity.result.IntentSenderRequest 8 | import androidx.annotation.RequiresApi 9 | import com.d4rk.android.libs.apptoolkit.notifications.managers.AppUpdateNotificationsManager 10 | import com.d4rk.cleaner.data.datastore.DataStore 11 | import com.google.android.play.core.appupdate.AppUpdateManager 12 | import kotlinx.coroutines.Dispatchers 13 | import kotlinx.coroutines.flow.first 14 | import kotlinx.coroutines.withContext 15 | 16 | /** 17 | * Concrete implementation of the main repository for managing application settings and startup state. 18 | * 19 | * @property dataStore The data store used to persist settings and startup information. 20 | * @property application The application context. 21 | */ 22 | class MainRepository(dataStore : DataStore , application : Application) : MainRepositoryImplementation(application = application , dataStore = dataStore) { 23 | 24 | suspend fun checkForUpdates( 25 | updateResultLauncher : ActivityResultLauncher , 26 | appUpdateManager : AppUpdateManager , 27 | ) { 28 | withContext(Dispatchers.IO) { 29 | checkForUpdatesImplementation(updateResultLauncher = updateResultLauncher , appUpdateManager = appUpdateManager) 30 | } 31 | } 32 | 33 | /** 34 | * Checks the application startup state and performs actions based on whether it's the first launch. 35 | * 36 | * This function checks if the app is launched for the first time and invokes the `onSuccess` callback 37 | * with the result on the main thread. 38 | * 39 | * @param onSuccess A callback function that receives a boolean indicating if it's the first launch. 40 | */ 41 | suspend fun checkAndHandleStartupRepository(onSuccess : (Boolean) -> Unit) { 42 | withContext(Dispatchers.IO) { 43 | val isFirstTime : Boolean = checkStartupImplementation() 44 | withContext(Dispatchers.Main) { 45 | onSuccess(isFirstTime) 46 | } 47 | } 48 | } 49 | 50 | @RequiresApi(Build.VERSION_CODES.O) 51 | suspend fun checkAndScheduleUpdateNotificationsRepository(appUpdateNotificationsManager : AppUpdateNotificationsManager) { 52 | withContext(Dispatchers.IO) { 53 | checkAndScheduleUpdateNotificationsImplementation(appUpdateNotificationsManager = appUpdateNotificationsManager) 54 | } 55 | } 56 | 57 | suspend fun checkAppUsageNotificationsRepository(context : Context) { 58 | withContext(Dispatchers.IO) { 59 | checkAppUsageNotificationsManagerImplementation(context = context) 60 | } 61 | } 62 | 63 | /** 64 | * Sets up Firebase Analytics and Crashlytics based on stored settings. 65 | * 66 | * This function retrieves the "usageAndDiagnostics" setting from the data store and configures 67 | * Firebase Analytics and Crashlytics accordingly. 68 | */ 69 | suspend fun setupSettingsRepository() { 70 | withContext(Dispatchers.IO) { 71 | val isEnabled : Boolean = dataStore.usageAndDiagnostics.first() 72 | withContext(Dispatchers.Main) { 73 | setupDiagnosticSettingsImplementation(isEnabled = isEnabled) 74 | } 75 | } 76 | } 77 | } -------------------------------------------------------------------------------- /app/src/main/kotlin/com/d4rk/cleaner/ui/screens/main/repository/MainRepositoryImplementation.kt: -------------------------------------------------------------------------------- 1 | package com.d4rk.cleaner.ui.screens.main.repository 2 | 3 | import android.app.Activity 4 | import android.app.Application 5 | import android.content.Context 6 | import android.os.Build 7 | import androidx.activity.result.ActivityResultLauncher 8 | import androidx.activity.result.IntentSenderRequest 9 | import androidx.annotation.RequiresApi 10 | import com.d4rk.android.libs.apptoolkit.notifications.managers.AppUpdateNotificationsManager 11 | import com.d4rk.android.libs.apptoolkit.notifications.managers.AppUsageNotificationsManager 12 | import com.d4rk.cleaner.R 13 | import com.d4rk.cleaner.data.datastore.DataStore 14 | import com.google.android.play.core.appupdate.AppUpdateInfo 15 | import com.google.android.play.core.appupdate.AppUpdateManager 16 | import com.google.android.play.core.appupdate.AppUpdateOptions 17 | import com.google.android.play.core.install.model.ActivityResult 18 | import com.google.android.play.core.install.model.AppUpdateType 19 | import com.google.android.play.core.install.model.UpdateAvailability 20 | import com.google.firebase.analytics.FirebaseAnalytics 21 | import com.google.firebase.crashlytics.FirebaseCrashlytics 22 | import kotlinx.coroutines.flow.first 23 | import kotlinx.coroutines.tasks.await 24 | 25 | /** 26 | * Abstract base class for repository implementations related to main application functionality. 27 | * 28 | * This class provides common functionality for managing application startup state. 29 | * 30 | * @property application The application context. 31 | */ 32 | abstract class MainRepositoryImplementation(val application : Application , val dataStore : DataStore) { 33 | 34 | /** 35 | * Checks if the application is being launched for the first time. 36 | * 37 | * This function retrieves the startup state from a data store and updates it if it's the first launch. 38 | * 39 | * @return `true` if it's the first launch, `false` otherwise. 40 | */ 41 | suspend fun checkStartupImplementation() : Boolean { 42 | val isFirstTime : Boolean = dataStore.startup.first() 43 | if (isFirstTime) { 44 | dataStore.saveStartup(isFirstTime = false) 45 | } 46 | return isFirstTime 47 | } 48 | 49 | /** 50 | * Configures Firebase Analytics and Crashlytics data collection. 51 | * 52 | * Enables or disables data collection for both Firebase Analytics and Crashlytics 53 | * based on the provided flag. 54 | * 55 | * @param isEnabled `true` to enable data collection, `false` to disable. 56 | */ 57 | fun setupDiagnosticSettingsImplementation(isEnabled : Boolean) { 58 | FirebaseAnalytics.getInstance(application).setAnalyticsCollectionEnabled(isEnabled) 59 | FirebaseCrashlytics.getInstance().isCrashlyticsCollectionEnabled = isEnabled 60 | } 61 | 62 | suspend fun checkForUpdatesImplementation( 63 | appUpdateManager : AppUpdateManager , updateResultLauncher : ActivityResultLauncher 64 | ) : Int { 65 | return runCatching { 66 | val appUpdateInfo : AppUpdateInfo = appUpdateManager.appUpdateInfo.await() 67 | if (appUpdateInfo.updateAvailability() == UpdateAvailability.UPDATE_AVAILABLE) { 68 | val stalenessDays = appUpdateInfo.clientVersionStalenessDays() ?: 0 69 | val updateType = if (stalenessDays > 90) { 70 | AppUpdateType.IMMEDIATE 71 | } 72 | else { 73 | AppUpdateType.FLEXIBLE 74 | } 75 | 76 | val appUpdateOptions : AppUpdateOptions = AppUpdateOptions.newBuilder(updateType).build() 77 | 78 | val didStart : Boolean = appUpdateManager.startUpdateFlowForResult( 79 | appUpdateInfo , updateResultLauncher , appUpdateOptions 80 | ) 81 | 82 | if (didStart) return@runCatching Activity.RESULT_OK 83 | } 84 | Activity.RESULT_CANCELED 85 | }.getOrElse { 86 | ActivityResult.RESULT_IN_APP_UPDATE_FAILED 87 | } 88 | } 89 | 90 | 91 | @RequiresApi(Build.VERSION_CODES.O) 92 | fun checkAndScheduleUpdateNotificationsImplementation(appUpdateNotificationsManager : AppUpdateNotificationsManager) { 93 | appUpdateNotificationsManager.checkAndSendUpdateNotification() 94 | } 95 | 96 | fun checkAppUsageNotificationsManagerImplementation(context : Context) { 97 | val appUsageNotificationsManager = AppUsageNotificationsManager(context = context) 98 | appUsageNotificationsManager.scheduleAppUsageCheck(notificationSummary = R.string.summary_notification_last_time_used) 99 | } 100 | } -------------------------------------------------------------------------------- /app/src/main/kotlin/com/d4rk/cleaner/ui/screens/memory/MemoryManagerViewModel.kt: -------------------------------------------------------------------------------- 1 | package com.d4rk.cleaner.ui.screens.memory 2 | 3 | import android.app.Application 4 | import androidx.lifecycle.viewModelScope 5 | import com.d4rk.cleaner.data.model.ui.screens.UiMemoryManagerModel 6 | import com.d4rk.cleaner.ui.screens.memory.repository.MemoryManagerRepository 7 | import com.d4rk.cleaner.ui.viewmodel.BaseViewModel 8 | import kotlinx.coroutines.delay 9 | import kotlinx.coroutines.flow.MutableStateFlow 10 | import kotlinx.coroutines.flow.StateFlow 11 | import kotlinx.coroutines.flow.asStateFlow 12 | import kotlinx.coroutines.flow.update 13 | import kotlinx.coroutines.launch 14 | 15 | /** 16 | * ViewModel for managing and providing information about device memory (RAM and storage). 17 | */ 18 | class MemoryManagerViewModel( 19 | application : Application , 20 | ) : BaseViewModel(application) { 21 | private val repository = MemoryManagerRepository(application) 22 | private val _uiMemoryManagerModel = MutableStateFlow(UiMemoryManagerModel()) 23 | val uiMemoryManagerModel : StateFlow = _uiMemoryManagerModel.asStateFlow() 24 | 25 | init { 26 | loadMemoryData() 27 | 28 | viewModelScope.launch(context = coroutineExceptionHandler) { 29 | while (true) { 30 | delay(timeMillis = 5000) 31 | updateRamMemoryData() 32 | } 33 | } 34 | } 35 | 36 | private fun loadMemoryData() { 37 | viewModelScope.launch(context = coroutineExceptionHandler) { 38 | showLoading() 39 | repository.getMemoryManagerData { uiMemoryData -> 40 | _uiMemoryManagerModel.update { uiMemoryData } 41 | } 42 | hideLoading() 43 | } 44 | } 45 | 46 | private fun updateRamMemoryData() { 47 | viewModelScope.launch(context = coroutineExceptionHandler) { 48 | repository.getRamInfo { ramInfo -> 49 | _uiMemoryManagerModel.update { it.copy(ramInfo = ramInfo) } 50 | } 51 | } 52 | } 53 | 54 | fun toggleListExpanded() { 55 | _uiMemoryManagerModel.update { 56 | it.copy( 57 | listExpanded = ! it.listExpanded 58 | ) 59 | } 60 | } 61 | } -------------------------------------------------------------------------------- /app/src/main/kotlin/com/d4rk/cleaner/ui/screens/memory/repository/MemoryManagerRepository.kt: -------------------------------------------------------------------------------- 1 | package com.d4rk.cleaner.ui.screens.memory.repository 2 | 3 | import android.app.Application 4 | import com.d4rk.cleaner.data.model.ui.memorymanager.RamInfo 5 | import com.d4rk.cleaner.data.model.ui.memorymanager.StorageInfo 6 | import com.d4rk.cleaner.data.model.ui.screens.UiMemoryManagerModel 7 | import kotlinx.coroutines.Deferred 8 | import kotlinx.coroutines.Dispatchers 9 | import kotlinx.coroutines.async 10 | import kotlinx.coroutines.withContext 11 | 12 | class MemoryManagerRepository(application : Application) : 13 | MemoryManagerRepositoryImplementation(application) { 14 | 15 | suspend fun getMemoryManagerData(onSuccess : (UiMemoryManagerModel) -> Unit) { 16 | withContext(Dispatchers.IO) { 17 | 18 | val storageInfoDeferred : Deferred = async { getStorageInfo() } 19 | val ramInfoDeferred : Deferred = async { getRamInfo() } 20 | 21 | val storageInfo : StorageInfo = storageInfoDeferred.await() 22 | val ramInfo : RamInfo = ramInfoDeferred.await() 23 | withContext(Dispatchers.Main) { 24 | onSuccess(UiMemoryManagerModel(storageInfo = storageInfo , ramInfo = ramInfo)) 25 | } 26 | } 27 | } 28 | 29 | suspend fun getRamInfo(onSuccess : (RamInfo) -> Unit) { 30 | withContext(Dispatchers.IO) { 31 | val ramInfo : RamInfo = getRamInfo() 32 | withContext(Dispatchers.Main) { 33 | onSuccess(ramInfo) 34 | } 35 | } 36 | } 37 | } -------------------------------------------------------------------------------- /app/src/main/kotlin/com/d4rk/cleaner/ui/screens/nofilesfound/NoFilesFoundScreen.kt: -------------------------------------------------------------------------------- 1 | package com.d4rk.cleaner.ui.screens.nofilesfound 2 | 3 | import androidx.compose.foundation.layout.Box 4 | import androidx.compose.foundation.layout.Column 5 | import androidx.compose.foundation.layout.fillMaxSize 6 | import androidx.compose.foundation.layout.size 7 | import androidx.compose.material.icons.Icons 8 | import androidx.compose.material.icons.outlined.FolderOff 9 | import androidx.compose.material.icons.outlined.Refresh 10 | import androidx.compose.material3.ButtonDefaults 11 | import androidx.compose.material3.Icon 12 | import androidx.compose.material3.MaterialTheme 13 | import androidx.compose.material3.OutlinedButton 14 | import androidx.compose.material3.Text 15 | import androidx.compose.runtime.Composable 16 | import androidx.compose.ui.Alignment 17 | import androidx.compose.ui.Modifier 18 | import androidx.compose.ui.res.stringResource 19 | import androidx.compose.ui.unit.dp 20 | import com.d4rk.android.libs.apptoolkit.ui.components.modifiers.bounceClick 21 | import com.d4rk.android.libs.apptoolkit.ui.components.spacers.ButtonIconSpacer 22 | import com.d4rk.android.libs.apptoolkit.ui.components.spacers.LargeVerticalSpacer 23 | import com.d4rk.cleaner.R 24 | import com.d4rk.cleaner.ui.screens.home.HomeViewModel 25 | 26 | @Composable 27 | fun NoFilesFoundScreen(viewModel : HomeViewModel) { 28 | Box(modifier = Modifier.fillMaxSize() , contentAlignment = Alignment.Center) { 29 | Column(horizontalAlignment = Alignment.CenterHorizontally) { 30 | Icon( 31 | imageVector = Icons.Outlined.FolderOff , 32 | contentDescription = null , 33 | modifier = Modifier.size(size = 64.dp) , 34 | tint = MaterialTheme.colorScheme.onSurface 35 | ) 36 | LargeVerticalSpacer() 37 | Text( 38 | text = stringResource(id = R.string.no_files_found) , 39 | style = MaterialTheme.typography.bodyLarge , 40 | color = MaterialTheme.colorScheme.onSurface 41 | ) 42 | 43 | OutlinedButton(modifier = Modifier.bounceClick() , onClick = { 44 | viewModel.analyze() 45 | }) { 46 | Icon( 47 | modifier = Modifier.size(size = ButtonDefaults.IconSize) , 48 | imageVector = Icons.Outlined.Refresh , 49 | contentDescription = null 50 | ) 51 | ButtonIconSpacer() 52 | Text(text = stringResource(id = com.d4rk.android.libs.apptoolkit.R.string.try_again)) 53 | } 54 | } 55 | } 56 | } -------------------------------------------------------------------------------- /app/src/main/kotlin/com/d4rk/cleaner/ui/screens/settings/SettingsActivity.kt: -------------------------------------------------------------------------------- 1 | package com.d4rk.cleaner.ui.screens.settings 2 | 3 | import android.os.Bundle 4 | import androidx.activity.compose.setContent 5 | import androidx.activity.enableEdgeToEdge 6 | import androidx.appcompat.app.AppCompatActivity 7 | import androidx.compose.foundation.layout.fillMaxSize 8 | import androidx.compose.material3.MaterialTheme 9 | import androidx.compose.material3.Surface 10 | import androidx.compose.ui.Modifier 11 | import com.d4rk.cleaner.ui.screens.settings.display.theme.style.AppTheme 12 | 13 | class SettingsActivity : AppCompatActivity() { 14 | override fun onCreate(savedInstanceState : Bundle?) { 15 | super.onCreate(savedInstanceState) 16 | enableEdgeToEdge() 17 | setContent { 18 | AppTheme { 19 | Surface( 20 | modifier = Modifier.fillMaxSize() , color = MaterialTheme.colorScheme.background 21 | ) { 22 | SettingsComposable( 23 | activity = this@SettingsActivity 24 | ) 25 | } 26 | } 27 | } 28 | } 29 | } -------------------------------------------------------------------------------- /app/src/main/kotlin/com/d4rk/cleaner/ui/screens/settings/display/theme/style/Color.kt: -------------------------------------------------------------------------------- 1 | package com.d4rk.cleaner.ui.screens.settings.display.theme.style 2 | 3 | import androidx.compose.ui.graphics.Color 4 | 5 | val primaryLight = Color(color = 0xFF006D3B) 6 | val onPrimaryLight = Color(color = 0xFFFFFFFF) 7 | val primaryContainerLight = Color(color = 0xFF49E58C) 8 | val onPrimaryContainerLight = Color(color = 0xFF004221) 9 | val secondaryLight = Color(color = 0xFF326945) 10 | val onSecondaryLight = Color(color = 0xFFFFFFFF) 11 | val secondaryContainerLight = Color(color = 0xFFB7F3C5) 12 | val onSecondaryContainerLight = Color(color = 0xFF1B5332) 13 | val tertiaryLight = Color(color = 0xFF00658E) 14 | val onTertiaryLight = Color(color = 0xFFFFFFFF) 15 | val tertiaryContainerLight = Color(color = 0xFF8DD2FF) 16 | val onTertiaryContainerLight = Color(color = 0xFF003D57) 17 | val errorLight = Color(color = 0xFFBA1A1A) 18 | val onErrorLight = Color(color = 0xFFFFFFFF) 19 | val errorContainerLight = Color(color = 0xFFFFDAD6) 20 | val onErrorContainerLight = Color(color = 0xFF410002) 21 | val backgroundLight = Color(color = 0xFFF3FCF1) 22 | val onBackgroundLight = Color(color = 0xFF161D17) 23 | val surfaceLight = Color(color = 0xFFF3FCF1) 24 | val onSurfaceLight = Color(color = 0xFF161D17) 25 | val surfaceVariantLight = Color(color = 0xFFD7E7D7) 26 | val onSurfaceVariantLight = Color(color = 0xFF3C4A3F) 27 | val outlineLight = Color(color = 0xFF6C7B6E) 28 | val outlineVariantLight = Color(color = 0xFFBBCBBC) 29 | val scrimLight = Color(color = 0xFF000000) 30 | val inverseSurfaceLight = Color(color = 0xFF2A322B) 31 | val inverseOnSurfaceLight = Color(color = 0xFFEBF3E9) 32 | val inversePrimaryLight = Color(color = 0xFF43E188) 33 | val surfaceDimLight = Color(color = 0xFFD4DCD2) 34 | val surfaceBrightLight = Color(color = 0xFFF3FCF1) 35 | val surfaceContainerLowestLight = Color(color = 0xFFFFFFFF) 36 | val surfaceContainerLowLight = Color(color = 0xFFEDF6EB) 37 | val surfaceContainerLight = Color(color = 0xFFE8F0E6) 38 | val surfaceContainerHighLight = Color(color = 0xFFE2EBE0) 39 | val surfaceContainerHighestLight = Color(color = 0xFFDCE5DB) 40 | 41 | val primaryDark = Color(color = 0xFF79FFAA) 42 | val onPrimaryDark = Color(color = 0xFF00391C) 43 | val primaryContainerDark = Color(color = 0xFF33D57E) 44 | val onPrimaryContainerDark = Color(color = 0xFF00361A) 45 | val secondaryDark = Color(color = 0xFF99D4A8) 46 | val onSecondaryDark = Color(color = 0xFF00391C) 47 | val secondaryContainerDark = Color(color = 0xFF0A4626) 48 | val onSecondaryContainerDark = Color(color = 0xFFA3DEB1) 49 | val tertiaryDark = Color(color = 0xFFCEEAFF) 50 | val onTertiaryDark = Color(color = 0xFF00344C) 51 | val tertiaryContainerDark = Color(color = 0xFF70C5F9) 52 | val onTertiaryContainerDark = Color(color = 0xFF003249) 53 | val errorDark = Color(color = 0xFFFFB4AB) 54 | val onErrorDark = Color(color = 0xFF690005) 55 | val errorContainerDark = Color(color = 0xFF93000A) 56 | val onErrorContainerDark = Color(color = 0xFFFFDAD6) 57 | val backgroundDark = Color(color = 0xFF0E150F) 58 | val onBackgroundDark = Color(color = 0xFFDCE5DB) 59 | val surfaceDark = Color(color = 0xFF0E150F) 60 | val onSurfaceDark = Color(color = 0xFFDCE5DB) 61 | val surfaceVariantDark = Color(color = 0xFF3C4A3F) 62 | val onSurfaceVariantDark = Color(color = 0xFFBBCBBC) 63 | val outlineDark = Color(color = 0xFF869587) 64 | val outlineVariantDark = Color(color = 0xFF3C4A3F) 65 | val scrimDark = Color(color = 0xFF000000) 66 | val inverseSurfaceDark = Color(color = 0xFFDCE5DB) 67 | val inverseOnSurfaceDark = Color(color = 0xFF2A322B) 68 | val inversePrimaryDark = Color(color = 0xFF006D3B) 69 | val surfaceDimDark = Color(color = 0xFF0E150F) 70 | val surfaceBrightDark = Color(color = 0xFF333B34) 71 | val surfaceContainerLowestDark = Color(color = 0xFF08100A) 72 | val surfaceContainerLowDark = Color(color = 0xFF161D17) 73 | val surfaceContainerDark = Color(color = 0xFF1A211B) 74 | val surfaceContainerHighDark = Color(color = 0xFF242C25) 75 | val surfaceContainerHighestDark = Color(color = 0xFF2F3730) -------------------------------------------------------------------------------- /app/src/main/kotlin/com/d4rk/cleaner/ui/screens/settings/general/GeneralSettingsActivity.kt: -------------------------------------------------------------------------------- 1 | package com.d4rk.cleaner.ui.screens.settings.general 2 | 3 | import android.content.Context 4 | import android.content.Intent 5 | import android.os.Bundle 6 | import androidx.activity.compose.setContent 7 | import androidx.activity.enableEdgeToEdge 8 | import androidx.appcompat.app.AppCompatActivity 9 | import androidx.compose.foundation.layout.fillMaxSize 10 | import androidx.compose.material3.MaterialTheme 11 | import androidx.compose.material3.Surface 12 | import androidx.compose.ui.Modifier 13 | import com.d4rk.cleaner.ui.screens.settings.display.theme.style.AppTheme 14 | 15 | class GeneralSettingsActivity : AppCompatActivity() { 16 | 17 | companion object { 18 | private const val EXTRA_TITLE = "extra_title" 19 | private const val EXTRA_CONTENT = "extra_content" 20 | 21 | fun start(context: Context , title: String , content: SettingsContent) { 22 | val intent = Intent(context, GeneralSettingsActivity::class.java).apply { 23 | putExtra(EXTRA_TITLE , title) 24 | putExtra(EXTRA_CONTENT , content.name) 25 | } 26 | context.startActivity(intent) 27 | } 28 | } 29 | 30 | override fun onCreate(savedInstanceState: Bundle?) { 31 | super.onCreate(savedInstanceState) 32 | enableEdgeToEdge() 33 | 34 | val title = intent.getStringExtra(EXTRA_TITLE) ?: getString(com.d4rk.android.libs.apptoolkit.R.string.settings) 35 | val content = intent.getStringExtra(EXTRA_CONTENT)?.let { SettingsContent.valueOf(it) } 36 | 37 | setContent { 38 | AppTheme { 39 | Surface( 40 | modifier = Modifier.fillMaxSize() , 41 | color = MaterialTheme.colorScheme.background 42 | ) { 43 | GeneralSettingsScreen( 44 | title = title, 45 | content = content, 46 | onBackClicked = { finish() } 47 | ) 48 | } 49 | } 50 | } 51 | } 52 | } -------------------------------------------------------------------------------- /app/src/main/kotlin/com/d4rk/cleaner/ui/screens/settings/general/GeneralSettingsScreen.kt: -------------------------------------------------------------------------------- 1 | package com.d4rk.cleaner.ui.screens.settings.general 2 | 3 | import androidx.compose.material3.ExperimentalMaterial3Api 4 | import androidx.compose.runtime.Composable 5 | import com.d4rk.android.libs.apptoolkit.ui.components.navigation.LargeTopAppBarWithScaffold 6 | import com.d4rk.android.libs.apptoolkit.ui.screens.settings.about.AboutSettingsList 7 | import com.d4rk.android.libs.apptoolkit.ui.screens.settings.advanced.AdvancedSettingsList 8 | import com.d4rk.android.libs.apptoolkit.ui.screens.settings.display.DisplaySettingsList 9 | import com.d4rk.android.libs.apptoolkit.ui.screens.settings.display.theme.ThemeSettingsList 10 | import com.d4rk.android.libs.apptoolkit.ui.screens.settings.privacy.PrivacySettingsList 11 | import com.d4rk.android.libs.apptoolkit.ui.screens.settings.privacy.usage.UsageAndDiagnosticsList 12 | import com.d4rk.cleaner.ui.screens.settings.cleaning.CleaningSettingsList 13 | import com.d4rk.cleaner.utils.providers.AppAboutSettingsProvider 14 | import com.d4rk.cleaner.utils.providers.AppAdvancedSettingsProvider 15 | import com.d4rk.cleaner.utils.providers.AppDisplaySettingsProvider 16 | import com.d4rk.cleaner.utils.providers.AppPrivacySettingsProvider 17 | import com.d4rk.cleaner.utils.providers.AppUsageAndDiagnosticsProvider 18 | 19 | @OptIn(ExperimentalMaterial3Api::class) 20 | @Composable 21 | fun GeneralSettingsScreen( 22 | title : String , content : SettingsContent? , onBackClicked : () -> Unit 23 | ) { 24 | LargeTopAppBarWithScaffold(title = title , onBackClicked = onBackClicked) { paddingValues -> 25 | when (content) { 26 | SettingsContent.ABOUT -> AboutSettingsList( 27 | paddingValues = paddingValues , provider = AppAboutSettingsProvider() 28 | ) 29 | 30 | SettingsContent.ADVANCED -> AdvancedSettingsList( 31 | paddingValues = paddingValues , provider = AppAdvancedSettingsProvider() 32 | ) 33 | 34 | SettingsContent.DISPLAY -> DisplaySettingsList( 35 | paddingValues = paddingValues , provider = AppDisplaySettingsProvider() 36 | ) 37 | 38 | SettingsContent.PRIVACY -> PrivacySettingsList( 39 | paddingValues = paddingValues , provider = AppPrivacySettingsProvider() 40 | ) 41 | 42 | SettingsContent.CLEANING -> CleaningSettingsList( 43 | paddingValues = paddingValues 44 | ) 45 | 46 | SettingsContent.THEME -> ThemeSettingsList( 47 | paddingValues = paddingValues 48 | ) 49 | 50 | SettingsContent.USAGE_AND_DIAGNOSTICS -> UsageAndDiagnosticsList( 51 | paddingValues = paddingValues, 52 | provider = AppUsageAndDiagnosticsProvider() 53 | ) 54 | 55 | else -> {} 56 | } 57 | } 58 | } -------------------------------------------------------------------------------- /app/src/main/kotlin/com/d4rk/cleaner/ui/screens/settings/general/SettingsContent.kt: -------------------------------------------------------------------------------- 1 | package com.d4rk.cleaner.ui.screens.settings.general 2 | 3 | enum class SettingsContent { 4 | ABOUT, 5 | ADVANCED, 6 | DISPLAY, 7 | CLEANING, 8 | PRIVACY, 9 | THEME, 10 | USAGE_AND_DIAGNOSTICS, 11 | } -------------------------------------------------------------------------------- /app/src/main/kotlin/com/d4rk/cleaner/ui/screens/settings/privacy/ads/AdsSettingsActivity.kt: -------------------------------------------------------------------------------- 1 | package com.d4rk.cleaner.ui.screens.settings.privacy.ads 2 | 3 | import android.os.Bundle 4 | import androidx.activity.compose.setContent 5 | import androidx.activity.enableEdgeToEdge 6 | import androidx.appcompat.app.AppCompatActivity 7 | import androidx.compose.foundation.layout.fillMaxSize 8 | import androidx.compose.material3.MaterialTheme 9 | import androidx.compose.material3.Surface 10 | import androidx.compose.ui.Modifier 11 | import com.d4rk.cleaner.ui.screens.settings.display.theme.style.AppTheme 12 | import com.google.android.ump.ConsentForm 13 | import com.google.android.ump.ConsentInformation 14 | import com.google.android.ump.ConsentRequestParameters 15 | import com.google.android.ump.UserMessagingPlatform 16 | 17 | class AdsSettingsActivity : AppCompatActivity() { 18 | private lateinit var consentInformation : ConsentInformation 19 | private val isPrivacyOptionsRequired : Boolean 20 | get() = consentInformation.privacyOptionsRequirementStatus == ConsentInformation.PrivacyOptionsRequirementStatus.REQUIRED 21 | private lateinit var consentForm : ConsentForm 22 | 23 | override fun onCreate(savedInstanceState : Bundle?) { 24 | super.onCreate(savedInstanceState) 25 | enableEdgeToEdge() 26 | consentInformation = UserMessagingPlatform.getConsentInformation(this@AdsSettingsActivity) 27 | setContent { 28 | AppTheme { 29 | Surface( 30 | modifier = Modifier.fillMaxSize() , color = MaterialTheme.colorScheme.background 31 | ) { 32 | AdsSettingsScreen(activity = this@AdsSettingsActivity) 33 | } 34 | } 35 | } 36 | } 37 | 38 | fun openForm() { 39 | UserMessagingPlatform.loadConsentForm(this , { consentForm -> 40 | this.consentForm = consentForm 41 | if (consentInformation.consentStatus == ConsentInformation.ConsentStatus.REQUIRED || consentInformation.consentStatus == ConsentInformation.ConsentStatus.OBTAINED) { 42 | consentForm.show(this) { 43 | loadForm() 44 | } 45 | } 46 | } , {}) 47 | } 48 | 49 | private fun loadForm() { 50 | val params : ConsentRequestParameters = 51 | ConsentRequestParameters.Builder().setTagForUnderAgeOfConsent(false).build() 52 | consentInformation.requestConsentInfoUpdate(this@AdsSettingsActivity , params , { 53 | UserMessagingPlatform.loadAndShowConsentFormIfRequired( 54 | this@AdsSettingsActivity 55 | ) { 56 | if (isPrivacyOptionsRequired) { 57 | invalidateOptionsMenu() 58 | } 59 | } 60 | } , {}) 61 | } 62 | } -------------------------------------------------------------------------------- /app/src/main/kotlin/com/d4rk/cleaner/ui/screens/settings/privacy/permissions/PermissionsSettingsActivity.kt: -------------------------------------------------------------------------------- 1 | package com.d4rk.cleaner.ui.screens.settings.privacy.permissions 2 | 3 | import android.os.Bundle 4 | import androidx.activity.compose.setContent 5 | import androidx.activity.enableEdgeToEdge 6 | import androidx.appcompat.app.AppCompatActivity 7 | import androidx.compose.foundation.layout.fillMaxSize 8 | import androidx.compose.material3.MaterialTheme 9 | import androidx.compose.material3.Surface 10 | import androidx.compose.ui.Modifier 11 | import com.d4rk.cleaner.ui.screens.settings.display.theme.style.AppTheme 12 | 13 | class PermissionsSettingsActivity : AppCompatActivity() { 14 | override fun onCreate(savedInstanceState : Bundle?) { 15 | super.onCreate(savedInstanceState) 16 | enableEdgeToEdge() 17 | setContent { 18 | AppTheme { 19 | Surface( 20 | modifier = Modifier.fillMaxSize() , color = MaterialTheme.colorScheme.background 21 | ) { 22 | PermissionsSettingsScreen(activity = this@PermissionsSettingsActivity) 23 | } 24 | } 25 | } 26 | } 27 | } -------------------------------------------------------------------------------- /app/src/main/kotlin/com/d4rk/cleaner/ui/screens/startup/StartupActivity.kt: -------------------------------------------------------------------------------- 1 | package com.d4rk.cleaner.ui.screens.startup 2 | 3 | import android.os.Bundle 4 | import androidx.activity.compose.setContent 5 | import androidx.activity.enableEdgeToEdge 6 | import androidx.appcompat.app.AppCompatActivity 7 | import androidx.compose.foundation.layout.fillMaxSize 8 | import androidx.compose.material3.MaterialTheme 9 | import androidx.compose.material3.Surface 10 | import androidx.compose.ui.Modifier 11 | import com.d4rk.cleaner.ui.screens.settings.display.theme.style.AppTheme 12 | import com.google.android.ump.ConsentForm 13 | import com.google.android.ump.ConsentInformation 14 | import com.google.android.ump.ConsentRequestParameters 15 | import com.google.android.ump.UserMessagingPlatform 16 | import kotlinx.coroutines.flow.MutableStateFlow 17 | 18 | class StartupActivity : AppCompatActivity() { 19 | private lateinit var consentInformation : ConsentInformation 20 | private lateinit var consentForm : ConsentForm 21 | val consentFormShown = MutableStateFlow(value = false) 22 | override fun onCreate(savedInstanceState : Bundle?) { 23 | super.onCreate(savedInstanceState) 24 | enableEdgeToEdge() 25 | setContent { 26 | AppTheme { 27 | Surface( 28 | modifier = Modifier.fillMaxSize() , color = MaterialTheme.colorScheme.background 29 | ) { 30 | StartupComposable(activity = this@StartupActivity) 31 | } 32 | } 33 | } 34 | val params : ConsentRequestParameters = ConsentRequestParameters.Builder().setTagForUnderAgeOfConsent(false).build() 35 | consentInformation = UserMessagingPlatform.getConsentInformation(this) 36 | consentInformation.requestConsentInfoUpdate(this , params , { 37 | if (consentInformation.isConsentFormAvailable) { 38 | loadForm() 39 | } 40 | } , {}) 41 | } 42 | 43 | /** 44 | * Loads the consent form for user messaging platform (UMP) based on consent status. 45 | * 46 | * This function initiates the loading of the consent form using UserMessagingPlatform (UMP) API. 47 | * Upon successful loading of the consent form, it assigns the form to a local variable `consentForm`. 48 | * If user consent is required (`ConsentStatus.REQUIRED`), the form is displayed to the user. 49 | * If the consent status is not required or an error occurs during loading, the function handles this gracefully. 50 | * 51 | * @see com.google.android.gms.ads.UserMessagingPlatform 52 | * @see com.google.ads.consent.ConsentInformation 53 | */ 54 | private fun loadForm() { 55 | UserMessagingPlatform.loadConsentForm(this@StartupActivity , { consentForm -> 56 | this.consentForm = consentForm 57 | if (consentInformation.consentStatus == ConsentInformation.ConsentStatus.REQUIRED) { 58 | consentFormShown.value = true 59 | consentForm.show(this) { 60 | loadForm() 61 | } 62 | } 63 | } , {}) 64 | } 65 | } -------------------------------------------------------------------------------- /app/src/main/kotlin/com/d4rk/cleaner/ui/screens/support/SupportActivity.kt: -------------------------------------------------------------------------------- 1 | @file:Suppress("DEPRECATION") 2 | 3 | package com.d4rk.cleaner.ui.screens.support 4 | 5 | import android.os.Bundle 6 | import androidx.activity.compose.setContent 7 | import androidx.activity.enableEdgeToEdge 8 | import androidx.activity.viewModels 9 | import androidx.appcompat.app.AppCompatActivity 10 | import androidx.compose.foundation.layout.fillMaxSize 11 | import androidx.compose.material3.MaterialTheme 12 | import androidx.compose.material3.Surface 13 | import androidx.compose.ui.Modifier 14 | import com.android.billingclient.api.BillingClient 15 | import com.android.billingclient.api.BillingFlowParams 16 | import com.android.billingclient.api.SkuDetails 17 | import com.d4rk.cleaner.ui.screens.settings.display.theme.style.AppTheme 18 | 19 | class SupportActivity : AppCompatActivity() { 20 | private val viewModel : SupportViewModel by viewModels() 21 | 22 | override fun onCreate(savedInstanceState : Bundle?) { 23 | super.onCreate(savedInstanceState) 24 | enableEdgeToEdge() 25 | setContent { 26 | AppTheme { 27 | Surface( 28 | modifier = Modifier.fillMaxSize() , color = MaterialTheme.colorScheme.background 29 | ) { 30 | SupportComposable(viewModel , activity = this@SupportActivity) 31 | } 32 | } 33 | } 34 | } 35 | 36 | fun initiatePurchase( 37 | sku : String , skuDetailsMap : Map , billingClient : BillingClient 38 | ) { 39 | val skuDetails : SkuDetails? = skuDetailsMap[sku] 40 | if (skuDetails != null) { 41 | val flowParams : BillingFlowParams = 42 | BillingFlowParams.newBuilder().setSkuDetails(skuDetails).build() 43 | billingClient.launchBillingFlow(this , flowParams) 44 | } 45 | } 46 | } -------------------------------------------------------------------------------- /app/src/main/kotlin/com/d4rk/cleaner/ui/screens/support/SupportViewModel.kt: -------------------------------------------------------------------------------- 1 | @file:Suppress("DEPRECATION") 2 | 3 | package com.d4rk.cleaner.ui.screens.support 4 | 5 | import androidx.compose.runtime.mutableStateMapOf 6 | import androidx.lifecycle.ViewModel 7 | import androidx.lifecycle.viewModelScope 8 | import com.android.billingclient.api.BillingClient 9 | import com.android.billingclient.api.SkuDetails 10 | import com.android.billingclient.api.SkuDetailsParams 11 | import kotlinx.coroutines.Dispatchers 12 | import kotlinx.coroutines.launch 13 | 14 | class SupportViewModel : ViewModel() { 15 | private val _skuDetails = mutableStateMapOf() 16 | val skuDetails : Map = _skuDetails 17 | 18 | fun querySkuDetails(billingClient : BillingClient) { 19 | viewModelScope.launch(context = Dispatchers.IO) { 20 | val skuList : List = listOf( 21 | "low_donation" , "normal_donation" , "high_donation" , "extreme_donation" 22 | ) 23 | val params : SkuDetailsParams = SkuDetailsParams.newBuilder().setSkusList(skuList) 24 | .setType(BillingClient.SkuType.INAPP).build() 25 | 26 | billingClient.querySkuDetailsAsync(params) { billingResult , skuDetailsList -> 27 | if (billingResult.responseCode == BillingClient.BillingResponseCode.OK && skuDetailsList != null) { 28 | skuDetailsList.forEach { skuDetails -> 29 | _skuDetails[skuDetails.sku] = skuDetails 30 | } 31 | } 32 | } 33 | } 34 | } 35 | } -------------------------------------------------------------------------------- /app/src/main/kotlin/com/d4rk/cleaner/ui/screens/trash/TrashActivity.kt: -------------------------------------------------------------------------------- 1 | package com.d4rk.cleaner.ui.screens.trash 2 | 3 | import android.os.Bundle 4 | import androidx.activity.compose.setContent 5 | import androidx.activity.enableEdgeToEdge 6 | import androidx.appcompat.app.AppCompatActivity 7 | import androidx.compose.foundation.layout.fillMaxSize 8 | import androidx.compose.material3.MaterialTheme 9 | import androidx.compose.material3.Surface 10 | import androidx.compose.ui.Modifier 11 | import com.d4rk.cleaner.ui.screens.settings.display.theme.style.AppTheme 12 | 13 | class TrashActivity : AppCompatActivity() { 14 | 15 | override fun onCreate(savedInstanceState : Bundle?) { 16 | super.onCreate(savedInstanceState) 17 | enableEdgeToEdge() 18 | 19 | setContent { 20 | AppTheme { 21 | Surface( 22 | modifier = Modifier.fillMaxSize() , color = MaterialTheme.colorScheme.background 23 | ) { 24 | TrashScreen(activity = this@TrashActivity) 25 | } 26 | } 27 | } 28 | } 29 | } -------------------------------------------------------------------------------- /app/src/main/kotlin/com/d4rk/cleaner/ui/screens/trash/TrashViewModel.kt: -------------------------------------------------------------------------------- 1 | package com.d4rk.cleaner.ui.screens.trash 2 | 3 | import android.app.Application 4 | import androidx.lifecycle.viewModelScope 5 | import com.d4rk.cleaner.data.datastore.DataStore 6 | import com.d4rk.cleaner.data.model.ui.screens.UiTrashModel 7 | import com.d4rk.cleaner.ui.screens.home.repository.HomeRepository 8 | import com.d4rk.cleaner.ui.viewmodel.BaseViewModel 9 | import kotlinx.coroutines.Dispatchers 10 | import kotlinx.coroutines.flow.MutableStateFlow 11 | import kotlinx.coroutines.flow.StateFlow 12 | import kotlinx.coroutines.launch 13 | import java.io.File 14 | 15 | /** 16 | * ViewModel for the Trash screen. Manages the display and interaction with trashed files. 17 | * 18 | * @param application The application instance. 19 | * @author Mihai-Cristian Condrea 20 | */ 21 | class TrashViewModel(application : Application) : BaseViewModel(application) { 22 | private val repository = HomeRepository(DataStore(application) , application) 23 | private val _uiState = MutableStateFlow(UiTrashModel()) 24 | val uiState : StateFlow = _uiState 25 | 26 | init { 27 | loadTrashItems() 28 | } 29 | 30 | /** 31 | * Loads the list of trashed files from the repository. 32 | */ 33 | private fun loadTrashItems() { 34 | viewModelScope.launch(context = coroutineExceptionHandler) { 35 | showLoading() 36 | _uiState.value = _uiState.value.copy(trashFiles = repository.getTrashFiles()) 37 | hideLoading() 38 | } 39 | } 40 | 41 | /** 42 | * Handles changes in file selection state. 43 | * 44 | * @param file The file whose selection state has changed. 45 | * @param isChecked True if the file is selected, false otherwise. 46 | */ 47 | fun onFileSelectionChange(file : File , isChecked : Boolean) { 48 | viewModelScope.launch(context = coroutineExceptionHandler) { 49 | val updatedSelections : Map = _uiState.value.fileSelectionStates + (file to isChecked) 50 | _uiState.value = _uiState.value.copy(fileSelectionStates = updatedSelections , 51 | selectedFileCount = updatedSelections.count { it.value }) 52 | } 53 | } 54 | 55 | /** 56 | * Restores selected files from the trash. 57 | */ 58 | fun restoreFromTrash() { 59 | viewModelScope.launch(context = coroutineExceptionHandler) { 60 | showLoading() 61 | val filesToRestore : Set = _uiState.value.fileSelectionStates.filter { it.value }.keys 62 | val totalFileSizeToRestore : Long = filesToRestore.sumOf { it.length() } 63 | repository.restoreFromTrashRepository(filesToRestore = filesToRestore) { 64 | loadTrashItems() 65 | } 66 | repository.subtractTrashSize(size = totalFileSizeToRestore) 67 | hideLoading() 68 | } 69 | } 70 | 71 | /** 72 | * Permanently deletes selected files from the trash. 73 | */ 74 | fun clean() { 75 | viewModelScope.launch(context = Dispatchers.Default + coroutineExceptionHandler) { 76 | showLoading() 77 | val filesToDelete = _uiState.value.fileSelectionStates.filter { it.value }.keys 78 | val totalFileSizeToDelete = filesToDelete.sumOf { it.length() } 79 | with(repository) { 80 | deleteFilesRepository(filesToDelete) { 81 | loadTrashItems() 82 | } 83 | 84 | with(dataStore) { 85 | subtractTrashSize(totalFileSizeToDelete) 86 | addCleanedSpace(totalFileSizeToDelete) 87 | saveLastScanTimestamp(System.currentTimeMillis()) 88 | } 89 | } 90 | hideLoading() 91 | } 92 | } 93 | } -------------------------------------------------------------------------------- /app/src/main/kotlin/com/d4rk/cleaner/utils/cleaning/ImageUtils.kt: -------------------------------------------------------------------------------- 1 | package com.d4rk.cleaner.utils.cleaning 2 | 3 | import android.content.Context 4 | import android.content.res.Resources 5 | import com.d4rk.cleaner.R 6 | 7 | fun getFileIcon(extension : String , context : Context) : Int { 8 | val lowercaseExtension : String = extension.lowercase() 9 | val resources : Resources = context.resources 10 | return when (lowercaseExtension) { 11 | in resources.getStringArray(R.array.apk_extensions) -> { 12 | R.drawable.ic_apk_document 13 | } 14 | 15 | in resources.getStringArray(R.array.image_extensions) -> { 16 | R.drawable.ic_image 17 | } 18 | 19 | in resources.getStringArray(R.array.video_extensions) -> { 20 | R.drawable.ic_video_file 21 | } 22 | 23 | in resources.getStringArray(R.array.audio_extensions) -> { 24 | R.drawable.ic_audio_file 25 | } 26 | 27 | in resources.getStringArray(R.array.archive_extensions) -> { 28 | R.drawable.ic_archive_filter 29 | } 30 | 31 | else -> { 32 | R.drawable.ic_unknown_document 33 | } 34 | } 35 | } -------------------------------------------------------------------------------- /app/src/main/kotlin/com/d4rk/cleaner/utils/cleaning/StorageUtils.kt: -------------------------------------------------------------------------------- 1 | package com.d4rk.cleaner.utils.cleaning 2 | 3 | import android.app.usage.StorageStatsManager 4 | import android.content.Context 5 | import android.os.Build 6 | import android.os.Environment 7 | import android.os.StatFs 8 | import android.os.storage.StorageManager 9 | import android.os.storage.StorageVolume 10 | import java.util.Locale 11 | import java.util.UUID 12 | import kotlin.math.log10 13 | import kotlin.math.pow 14 | import kotlin.math.roundToInt 15 | 16 | /** 17 | * Utility object for managing and retrieving device storage information. 18 | */ 19 | object StorageUtils { 20 | 21 | /** 22 | * Retrieves storage information for the primary storage volume. 23 | * 24 | * This function calculates the used and total storage space in gigabytes (GB) and 25 | * the usage progress as a float value between 0 and 1. 26 | * The results are returned asynchronously via a callback function on the main thread.* 27 | * @param context The application context. 28 | * @param callback A function to receive the storage information: 29 | * - used: Used storage space in GB (rounded to nearest integer). 30 | * - total: Total storage space in GB (rounded to nearest integer). 31 | * - usageProgress: Storage usage progress as a float (0 to 1). 32 | */ 33 | 34 | fun getStorageInfo( 35 | context: Context, 36 | callback: (used: String, total: String, totalSpace: Long, usageProgress: Float, freeSize: Int) -> Unit 37 | ) { 38 | val storageManager : StorageManager = 39 | context.getSystemService(Context.STORAGE_SERVICE) as StorageManager 40 | val totalSize : Long 41 | val usedSize : Long 42 | if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) { 43 | val storageStatsManager : StorageStatsManager = 44 | context.getSystemService(Context.STORAGE_STATS_SERVICE) as StorageStatsManager 45 | val storageVolume : StorageVolume = storageManager.primaryStorageVolume 46 | val uuidStr : String? = storageVolume.uuid 47 | val uuid : UUID = 48 | if (uuidStr == null) StorageManager.UUID_DEFAULT else UUID.fromString(uuidStr) 49 | totalSize = storageStatsManager.getTotalBytes(uuid) 50 | usedSize = totalSize - storageStatsManager.getFreeBytes(uuid) 51 | } 52 | else { 53 | val statFs = StatFs(Environment.getExternalStorageDirectory().path) 54 | totalSize = statFs.blockSizeLong * statFs.blockCountLong 55 | usedSize = totalSize - (statFs.blockSizeLong * statFs.availableBlocksLong) 56 | } 57 | val usedFormatted : String = (usedSize / (1024.0 * 1024.0 * 1024.0)).roundToInt().toString() 58 | val totalFormatted : String = 59 | (totalSize / (1024.0 * 1024.0 * 1024.0)).roundToInt().toString() 60 | val usageProgress : Float = usedSize.toFloat() / totalSize.toFloat() 61 | val freeSize = totalSize - usedSize 62 | val freeSpacePercentage: Int = ((freeSize.toDouble() / totalSize.toDouble()) * 100).toInt() 63 | callback(usedFormatted, totalFormatted, totalSize, usageProgress, freeSpacePercentage) 64 | } 65 | 66 | /** 67 | * Formats a file size in bytes into a human-readable string with appropriate units (B, KB, MB, GB, TB). 68 | * 69 | * @param size The file size in bytes. 70 | * @return The formatted file size string (e.g., "123 MB", "4.56 GB"). 71 | */ 72 | fun formatSize(size : Long) : String { 73 | if (size <= 0) return "0 B" 74 | val units : Array = arrayOf("B" , "KB" , "MB" , "GB" , "TB") 75 | val digitGroups : Int = (log10(size.toDouble()) / log10(x = 1024.0)).toInt() 76 | val value : Double = size / 1024.0.pow(digitGroups.toDouble()) 77 | 78 | return if (value.compareTo(value.toLong()) == 0) { 79 | String.format(Locale.US , format = "%d %s" , value.toLong() , units[digitGroups]) 80 | } 81 | else { 82 | val decimalPart : Int = (value * 100).toInt() % 100 83 | if (decimalPart == 0) { 84 | String.format(Locale.US , format = "%d %s" , value.toLong() , units[digitGroups]) 85 | } 86 | else { 87 | String.format(Locale.US , format = "%.2f %s" , value , units[digitGroups]) 88 | } 89 | } 90 | } 91 | } -------------------------------------------------------------------------------- /app/src/main/kotlin/com/d4rk/cleaner/utils/constants/ads/AdsConstants.kt: -------------------------------------------------------------------------------- 1 | package com.d4rk.cleaner.utils.constants.ads 2 | 3 | import com.d4rk.android.libs.apptoolkit.utils.constants.ads.DebugAdsConstants 4 | import com.d4rk.cleaner.BuildConfig 5 | 6 | object AdsConstants { 7 | 8 | val BANNER_AD_UNIT_ID: String 9 | get() = if (BuildConfig.DEBUG) { 10 | DebugAdsConstants.BANNER_AD_UNIT_ID 11 | } else { 12 | "ca-app-pub-5294151573817700/7844185090" 13 | } 14 | 15 | val APP_OPEN_UNIT_ID: String 16 | get() = if (BuildConfig.DEBUG) { 17 | DebugAdsConstants.APP_OPEN_AD_UNIT_ID 18 | } else { 19 | "ca-app-pub-5294151573817700/9563492881" 20 | } 21 | } -------------------------------------------------------------------------------- /app/src/main/kotlin/com/d4rk/cleaner/utils/constants/cleaning/ExtensionsConstants.kt: -------------------------------------------------------------------------------- 1 | package com.d4rk.cleaner.utils.constants.cleaning 2 | 3 | object ExtensionsConstants { 4 | const val GENERIC_EXTENSIONS = "generic_extensions" 5 | const val ARCHIVE_EXTENSIONS = "archive_extensions" 6 | const val APK_EXTENSIONS = "apk_extensions" 7 | const val EMPTY_FOLDERS = "empty_folders" 8 | const val IMAGE_EXTENSIONS = "image_extensions" 9 | const val AUDIO_EXTENSIONS = "audio_extensions" 10 | const val VIDEO_EXTENSIONS = "video_extensions" 11 | const val WINDOWS_EXTENSIONS = "windows_extensions" 12 | const val OFFICE_EXTENSIONS = "office_extensions" 13 | const val FONT_EXTENSIONS = "font_extensions" 14 | const val OTHER_EXTENSIONS = "other_extensions" 15 | } -------------------------------------------------------------------------------- /app/src/main/kotlin/com/d4rk/cleaner/utils/constants/datastore/DataStoreNamesConstants.kt: -------------------------------------------------------------------------------- 1 | package com.d4rk.cleaner.utils.constants.datastore 2 | 3 | import com.d4rk.android.libs.apptoolkit.utils.constants.datastore.DataStoreNamesConstants 4 | 5 | object AppDataStoreConstants : DataStoreNamesConstants() { 6 | const val DATA_STORE_STARTUP_PAGE = "startup_page" 7 | const val DATA_STORE_SHOW_BOTTOM_BAR_LABELS = "show_bottom_bar_labels" 8 | const val DATA_STORE_GENERIC_FILTER = "generic_filter" 9 | const val DATA_STORE_DELETE_EMPTY_FOLDERS = "delete_empty_folders" 10 | const val DATA_STORE_DELETE_ARCHIVES = "delete_archives" 11 | const val DATA_STORE_DELETE_INVALID_MEDIA = "delete_invalid_media" 12 | const val DATA_STORE_DELETE_CORPSE_FILES = "delete_corpse_files" 13 | const val DATA_STORE_DELETE_APK_FILES = "delete_apk_files" 14 | const val DATA_STORE_DELETE_AUDIO_FILES = "delete_audio_files" 15 | const val DATA_STORE_DELETE_VIDEO_FILES = "delete_video_files" 16 | const val DATA_STORE_DELETE_IMAGE_FILES = "delete_image_files" 17 | const val DATA_STORE_DELETE_OFFICE_FILES = "delete_office_files" 18 | const val DATA_STORE_DELETE_WINDOWS_FILES = "delete_windows_files" 19 | const val DATA_STORE_DELETE_FONT_FILES = "delete_font_files" 20 | const val DATA_STORE_OTHER_EXTENSIONS = "other_extensions" 21 | const val DATA_STORE_CLIPBOARD_CLEAN = "clipboard_clean" 22 | } -------------------------------------------------------------------------------- /app/src/main/kotlin/com/d4rk/cleaner/utils/constants/permissions/AppPermissionsConstants.kt: -------------------------------------------------------------------------------- 1 | package com.d4rk.cleaner.utils.constants.permissions 2 | 3 | import com.d4rk.android.libs.apptoolkit.utils.constants.permissions.PermissionsConstants 4 | 5 | object AppPermissionsConstants : PermissionsConstants() { 6 | const val REQUEST_CODE_STORAGE_PERMISSIONS = 2 7 | } -------------------------------------------------------------------------------- /app/src/main/kotlin/com/d4rk/cleaner/utils/constants/ui/bottombar/BottomBarRoutes.kt: -------------------------------------------------------------------------------- 1 | package com.d4rk.cleaner.utils.constants.ui.bottombar 2 | 3 | object BottomBarRoutes { 4 | const val HOME = "home" 5 | const val APP_MANAGER = "app_manager" 6 | const val MEMORY_MANAGER = "memory_manager" 7 | } -------------------------------------------------------------------------------- /app/src/main/kotlin/com/d4rk/cleaner/utils/error/CrashlyticsErrorReporter.kt: -------------------------------------------------------------------------------- 1 | package com.d4rk.cleaner.utils.error 2 | 3 | import com.d4rk.android.libs.apptoolkit.utils.interfaces.ErrorReporter 4 | import com.google.firebase.crashlytics.FirebaseCrashlytics 5 | 6 | class CrashlyticsErrorReporter : ErrorReporter { 7 | 8 | private val crashlytics: FirebaseCrashlytics = FirebaseCrashlytics.getInstance() 9 | 10 | override fun recordException(throwable: Throwable, message: String?) { 11 | message?.let { 12 | crashlytics.setCustomKey("error_message", it) 13 | } 14 | crashlytics.recordException(throwable) 15 | } 16 | 17 | override fun setCustomKey(key: String, value: String) { 18 | crashlytics.setCustomKey(key, value) 19 | } 20 | } -------------------------------------------------------------------------------- /app/src/main/kotlin/com/d4rk/cleaner/utils/helpers/PermissionsHelper.kt: -------------------------------------------------------------------------------- 1 | package com.d4rk.cleaner.utils.helpers 2 | 3 | import android.Manifest 4 | import android.app.Activity 5 | import android.app.AppOpsManager 6 | import android.content.Context 7 | import android.content.Intent 8 | import android.content.pm.ApplicationInfo 9 | import android.content.pm.PackageManager 10 | import android.net.Uri 11 | import android.os.Build 12 | import android.os.Environment 13 | import android.provider.Settings 14 | import androidx.core.app.ActivityCompat 15 | import androidx.core.content.ContextCompat 16 | import com.d4rk.cleaner.utils.constants.permissions.AppPermissionsConstants 17 | 18 | /** 19 | * Utility class for handling runtime permissions. 20 | */ 21 | object PermissionsHelper { 22 | 23 | /** 24 | * Checks if the app has all necessary storage permissions. 25 | * 26 | * @param context The application context. 27 | * @return True if all required permissions are granted, false otherwise. 28 | */ 29 | fun hasStoragePermissions(context: Context): Boolean { 30 | val hasStoragePermissions: Boolean = when { 31 | Build.VERSION.SDK_INT <= Build.VERSION_CODES.Q -> ContextCompat.checkSelfPermission( 32 | context, Manifest.permission.WRITE_EXTERNAL_STORAGE 33 | ) == PackageManager.PERMISSION_GRANTED 34 | 35 | Build.VERSION.SDK_INT <= Build.VERSION_CODES.S_V2 -> ContextCompat.checkSelfPermission( 36 | context, Manifest.permission.READ_EXTERNAL_STORAGE 37 | ) == PackageManager.PERMISSION_GRANTED 38 | 39 | else -> true 40 | } 41 | 42 | val hasManageStoragePermission: Boolean = 43 | if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.R) { 44 | Environment.isExternalStorageManager() 45 | } else { 46 | true 47 | } 48 | 49 | return hasStoragePermissions && hasManageStoragePermission 50 | } 51 | 52 | /** 53 | * Requests the necessary storage permissions. 54 | * 55 | * @param activity The Activity instance required to request permissions. 56 | */ 57 | fun requestStoragePermissions(activity: Activity) { 58 | val requiredPermissions: MutableList = mutableListOf( 59 | Manifest.permission.WRITE_EXTERNAL_STORAGE, Manifest.permission.READ_EXTERNAL_STORAGE 60 | ) 61 | 62 | if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.R) { 63 | if (!Environment.isExternalStorageManager()) { 64 | val intent = Intent(Settings.ACTION_MANAGE_APP_ALL_FILES_ACCESS_PERMISSION) 65 | val uri: Uri = Uri.fromParts("package", activity.packageName, null) 66 | intent.data = uri 67 | activity.startActivity(intent) 68 | } 69 | 70 | } 71 | 72 | ActivityCompat.requestPermissions( 73 | activity, 74 | requiredPermissions.toTypedArray(), 75 | AppPermissionsConstants.REQUEST_CODE_STORAGE_PERMISSIONS 76 | ) 77 | } 78 | 79 | fun hasUsageAccessPermissions(context: Context): Boolean { 80 | val hasUsageStatsPermission: Boolean = 81 | if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.R) { 82 | isAccessGranted(context) 83 | } else { 84 | true 85 | } 86 | 87 | return hasUsageStatsPermission 88 | } 89 | 90 | fun requestUsageAccess(activity: Activity) { 91 | if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.R) { 92 | if (! isAccessGranted(activity)) { 93 | val intent = Intent(Settings.ACTION_USAGE_ACCESS_SETTINGS) 94 | activity.startActivity(intent) 95 | } 96 | } 97 | } 98 | 99 | /** 100 | * Checks if the app has access to usage statistics. 101 | * 102 | * @param context The application context. 103 | * @return True if access is granted, false otherwise. 104 | */ 105 | private fun isAccessGranted(context: Context): Boolean = try { 106 | val packageManager: PackageManager = context.packageManager 107 | val applicationInfo: ApplicationInfo = 108 | packageManager.getApplicationInfo(context.packageName, 0) 109 | val appOpsManager: AppOpsManager = 110 | context.getSystemService(Context.APP_OPS_SERVICE) as AppOpsManager 111 | @Suppress("DEPRECATION") val mode: Int = appOpsManager.checkOpNoThrow( 112 | AppOpsManager.OPSTR_GET_USAGE_STATS, applicationInfo.uid, applicationInfo.packageName 113 | ) 114 | mode == AppOpsManager.MODE_ALLOWED 115 | } catch (e: PackageManager.NameNotFoundException) { 116 | false 117 | } 118 | } -------------------------------------------------------------------------------- /app/src/main/kotlin/com/d4rk/cleaner/utils/helpers/TimeHelper.kt: -------------------------------------------------------------------------------- 1 | package com.d4rk.cleaner.utils.helpers 2 | 3 | import android.content.Context 4 | import com.d4rk.cleaner.R 5 | import java.text.SimpleDateFormat 6 | import java.util.Calendar 7 | import java.util.Date 8 | import java.util.Locale 9 | 10 | object TimeHelper { 11 | 12 | fun formatDate(context: Context, date: Date): String { 13 | val today = Calendar.getInstance() 14 | val yesterday = Calendar.getInstance() 15 | yesterday.add(Calendar.DAY_OF_YEAR, -1) 16 | 17 | val calendar = Calendar.getInstance() 18 | calendar.time = date 19 | 20 | return when { 21 | calendar.get(Calendar.YEAR) == today.get(Calendar.YEAR) && 22 | calendar.get(Calendar.DAY_OF_YEAR) == today.get(Calendar.DAY_OF_YEAR) -> { 23 | context.getString(R.string.today) 24 | } 25 | calendar.get(Calendar.YEAR) == yesterday.get(Calendar.YEAR) && 26 | calendar.get(Calendar.DAY_OF_YEAR) == yesterday.get(Calendar.DAY_OF_YEAR) -> { 27 | context.getString(R.string.yesterday) 28 | } 29 | calendar.get(Calendar.YEAR) == today.get(Calendar.YEAR) -> { 30 | SimpleDateFormat("EEE, MMM d", Locale.getDefault()).format(date) 31 | } 32 | calendar.get(Calendar.YEAR) == today.get(Calendar.YEAR) - 1 -> { 33 | context.getString(R.string.a_year_ago) 34 | } 35 | else -> { 36 | val yearsAgo = today.get(Calendar.YEAR) - calendar.get(Calendar.YEAR) 37 | context.resources.getQuantityString(R.plurals.years_ago, yearsAgo, yearsAgo) 38 | } 39 | } 40 | } 41 | } -------------------------------------------------------------------------------- /app/src/main/kotlin/com/d4rk/cleaner/utils/imageoptimizer/ImageOptimizerUtils.kt: -------------------------------------------------------------------------------- 1 | package com.d4rk.cleaner.utils.imageoptimizer 2 | 3 | import com.d4rk.cleaner.data.model.ui.imageoptimizer.CompressionLevel 4 | 5 | // TODO move in quick compression repository impl 6 | fun getCompressionLevelFromSliderValue(sliderValue: Float): CompressionLevel { 7 | return when { 8 | sliderValue < 33f -> CompressionLevel.LOW 9 | sliderValue < 66f -> CompressionLevel.MEDIUM 10 | else -> CompressionLevel.HIGH 11 | } 12 | } -------------------------------------------------------------------------------- /app/src/main/kotlin/com/d4rk/cleaner/utils/providers/AppAboutSettingsProvider.kt: -------------------------------------------------------------------------------- 1 | package com.d4rk.cleaner.utils.providers 2 | 3 | import android.content.Context 4 | import com.d4rk.cleaner.R 5 | import android.os.Build 6 | import com.d4rk.android.libs.apptoolkit.utils.interfaces.providers.AboutSettingsProvider 7 | import com.d4rk.cleaner.BuildConfig 8 | import com.d4rk.cleaner.data.core.AppCoreManager 9 | 10 | class AppAboutSettingsProvider : AboutSettingsProvider { 11 | 12 | private val context : Context = AppCoreManager.instance 13 | 14 | override val appName : String 15 | get() = context.getString(R.string.app_name) 16 | 17 | override val packageName : String 18 | get() = context.packageName 19 | 20 | 21 | override val appVersion : String 22 | get() = BuildConfig.VERSION_NAME 23 | 24 | override val appVersionCode : Int 25 | get() { 26 | return BuildConfig.VERSION_CODE 27 | } 28 | 29 | override val copyrightText : String 30 | get() = context.getString(R.string.copyright) 31 | 32 | override val deviceInfo : String 33 | get() { 34 | return context.getString( 35 | com.d4rk.android.libs.apptoolkit.R.string.app_build , 36 | "${context.getString(com.d4rk.android.libs.apptoolkit.R.string.manufacturer)} ${Build.MANUFACTURER}" , 37 | "${context.getString(com.d4rk.android.libs.apptoolkit.R.string.device_model)} ${Build.MODEL}" , 38 | "${context.getString(com.d4rk.android.libs.apptoolkit.R.string.android_version)} ${Build.VERSION.RELEASE}" , 39 | "${context.getString(com.d4rk.android.libs.apptoolkit.R.string.api_level)} ${Build.VERSION.SDK_INT}" , 40 | "${context.getString(com.d4rk.android.libs.apptoolkit.R.string.arch)} ${Build.SUPPORTED_ABIS.joinToString()}" , 41 | if (BuildConfig.DEBUG) context.getString(com.d4rk.android.libs.apptoolkit.R.string.debug) else context.getString(com.d4rk.android.libs.apptoolkit.R.string.release) 42 | ) 43 | } 44 | } -------------------------------------------------------------------------------- /app/src/main/kotlin/com/d4rk/cleaner/utils/providers/AppAdvancedSettingsProvider.kt: -------------------------------------------------------------------------------- 1 | package com.d4rk.cleaner.utils.providers 2 | 3 | import com.d4rk.android.libs.apptoolkit.utils.interfaces.providers.AdvancedSettingsProvider 4 | import com.d4rk.cleaner.data.core.AppCoreManager 5 | 6 | class AppAdvancedSettingsProvider : AdvancedSettingsProvider { 7 | override val bugReportUrl: String 8 | get() = "https://github.com/D4rK7355608/${AppCoreManager.instance.packageName}/issues/new" 9 | } -------------------------------------------------------------------------------- /app/src/main/kotlin/com/d4rk/cleaner/utils/providers/AppDisplaySettingsProvider.kt: -------------------------------------------------------------------------------- 1 | package com.d4rk.cleaner.utils.providers 2 | 3 | import android.content.Intent 4 | import androidx.compose.runtime.Composable 5 | import com.d4rk.android.libs.apptoolkit.utils.interfaces.providers.DisplaySettingsProvider 6 | import com.d4rk.cleaner.data.core.AppCoreManager 7 | import com.d4rk.cleaner.ui.components.dialogs.SelectLanguageAlertDialog 8 | import com.d4rk.cleaner.ui.components.dialogs.SelectStartupScreenAlertDialog 9 | import com.d4rk.cleaner.ui.screens.settings.general.GeneralSettingsActivity 10 | import com.d4rk.cleaner.ui.screens.settings.general.SettingsContent 11 | 12 | class AppDisplaySettingsProvider : DisplaySettingsProvider { 13 | 14 | override val supportsStartupPage : Boolean = true 15 | 16 | @Composable 17 | override fun LanguageSelectionDialog(onDismiss : () -> Unit , onLanguageSelected : (String) -> Unit) { 18 | SelectLanguageAlertDialog( 19 | dataStore = AppCoreManager.dataStore , onDismiss = onDismiss , onLanguageSelected = onLanguageSelected 20 | ) 21 | } 22 | 23 | @Composable 24 | override fun StartupPageDialog(onDismiss : () -> Unit , onStartupSelected : (String) -> Unit) { 25 | SelectStartupScreenAlertDialog( 26 | dataStore = AppCoreManager.dataStore , onDismiss = onDismiss , onStartupSelected = onStartupSelected 27 | ) 28 | } 29 | 30 | override fun openThemeSettings() { 31 | val context : AppCoreManager = AppCoreManager.instance 32 | val intent : Intent = Intent(context , GeneralSettingsActivity::class.java).apply { 33 | putExtra("extra_title" , context.getString(com.d4rk.android.libs.apptoolkit.R.string.dark_theme)) 34 | putExtra("extra_content" , SettingsContent.THEME.name) 35 | addFlags(Intent.FLAG_ACTIVITY_NEW_TASK) 36 | } 37 | context.startActivity(intent) 38 | } 39 | } -------------------------------------------------------------------------------- /app/src/main/kotlin/com/d4rk/cleaner/utils/providers/AppPrivacySettingsProvider.kt: -------------------------------------------------------------------------------- 1 | package com.d4rk.cleaner.utils.providers 2 | 3 | import android.content.Context 4 | import android.content.Intent 5 | import com.d4rk.android.libs.apptoolkit.utils.helpers.IntentsHelper 6 | import com.d4rk.android.libs.apptoolkit.utils.interfaces.providers.PrivacySettingsProvider 7 | import com.d4rk.cleaner.data.core.AppCoreManager 8 | import com.d4rk.cleaner.ui.screens.settings.general.GeneralSettingsActivity 9 | import com.d4rk.cleaner.ui.screens.settings.general.SettingsContent 10 | import com.d4rk.cleaner.ui.screens.settings.privacy.ads.AdsSettingsActivity 11 | import com.d4rk.cleaner.ui.screens.settings.privacy.permissions.PermissionsSettingsActivity 12 | 13 | class AppPrivacySettingsProvider : PrivacySettingsProvider { 14 | 15 | val context : Context = AppCoreManager.instance 16 | 17 | override fun openPermissionsScreen() { 18 | IntentsHelper.openActivity(context = context , activityClass = PermissionsSettingsActivity::class.java) 19 | } 20 | 21 | override fun openAdsScreen() { 22 | IntentsHelper.openActivity(context = context , activityClass = AdsSettingsActivity::class.java) 23 | } 24 | 25 | override fun openUsageAndDiagnosticsScreen() { 26 | 27 | val intent : Intent = Intent(context , GeneralSettingsActivity::class.java).apply { 28 | putExtra("extra_title" , context.getString(com.d4rk.android.libs.apptoolkit.R.string.usage_and_diagnostics)) 29 | putExtra("extra_content" , SettingsContent.USAGE_AND_DIAGNOSTICS.name) 30 | addFlags(Intent.FLAG_ACTIVITY_NEW_TASK) 31 | } 32 | context.startActivity(intent) 33 | } 34 | } -------------------------------------------------------------------------------- /app/src/main/kotlin/com/d4rk/cleaner/utils/providers/AppUsageAndDiagnosticsProvider.kt: -------------------------------------------------------------------------------- 1 | package com.d4rk.cleaner.utils.providers 2 | 3 | import com.d4rk.android.libs.apptoolkit.utils.interfaces.providers.UsageAndDiagnosticsSettingsProvider 4 | import com.d4rk.cleaner.BuildConfig 5 | 6 | class AppUsageAndDiagnosticsProvider : UsageAndDiagnosticsSettingsProvider { 7 | 8 | override val isDebugBuild : Boolean 9 | get() { 10 | return BuildConfig.DEBUG 11 | } 12 | } -------------------------------------------------------------------------------- /app/src/main/play/contact-email.txt: -------------------------------------------------------------------------------- 1 | d4rk7355608@gmail.com -------------------------------------------------------------------------------- /app/src/main/play/contact-website.txt: -------------------------------------------------------------------------------- 1 | https://sites.google.com/view/d4rk7355608/home -------------------------------------------------------------------------------- /app/src/main/play/default-language.txt: -------------------------------------------------------------------------------- 1 | en-US -------------------------------------------------------------------------------- /app/src/main/play/keys/com.d4rk.cleaner.jks: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/D4rK7355608/com.d4rk.cleaner/68a6c7e23b281de484344db08e29829afb224a92/app/src/main/play/keys/com.d4rk.cleaner.jks -------------------------------------------------------------------------------- /app/src/main/play/keys/com.d4rk.cleaner.plus.jks: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/D4rK7355608/com.d4rk.cleaner/68a6c7e23b281de484344db08e29829afb224a92/app/src/main/play/keys/com.d4rk.cleaner.plus.jks -------------------------------------------------------------------------------- /app/src/main/play/keys/com.d4rk.cleaner.plus_private_key.pepk: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/D4rK7355608/com.d4rk.cleaner/68a6c7e23b281de484344db08e29829afb224a92/app/src/main/play/keys/com.d4rk.cleaner.plus_private_key.pepk -------------------------------------------------------------------------------- /app/src/main/play/listings/en-US/full-description.txt: -------------------------------------------------------------------------------- 1 | Cleaner 2 | 3 | Cleaner is an Android app that helps you free up space and manage your device. It removes junk files, cache and other unwanted data from your device, so you can get back the storage and performance you need. 4 | 5 | It includes a variety of other features to help you keep your phone clean and running smoothly, such as a clipboard cleaner, app manager, whitelist, and customizable cleaning filters. With Cleaner, you can easily keep your phone in top condition, without having to worry about complicated settings or rooting your device. 6 | 7 | Our app is designed to be simple and easy to use, while also being fast and lightweight. Plus, it's free and open-source software! 8 | 9 | Features 10 | • App management 11 | • Image optimization 12 | • Clean empty folders 13 | • Clean logs, temporary files, caches, and corpse files 14 | • Clean advertisement folders 15 | • Clean archive files 16 | • Clean invalid media 17 | • Clean APK files 18 | • Clean clipboard contents 19 | 20 | Benefits 21 | • Free up space on your device 22 | • Improve performance 23 | • Keep your device organized and running smoothly 24 | • Protect your privacy by removing sensitive data 25 | 26 | How it works 27 | Cleaner scans your device for junk files, cache, and other unwanted data. It then gives you the option to remove these files, or to add them to a whitelist so that they are not removed in the future. You can also schedule Cleaner to run automatically on a daily basis, so you can keep your device clean and running smoothly without any effort. 28 | 29 | Get started today 30 | Download Cleaner from the Google Play Store today and start freeing up space and improving the performance of your Android device. It's free and easy to use, and it's the perfect way to keep your device running at its best. 31 | 32 | Feedback 33 | We are constantly updating and improving Cleaner to give you the best possible experience. If you have any suggested features or improvements, please leave a review. In case something is not working correctly please let me know. When posting a low rating please describe what is wrong to give the possibility to fix that issue. 34 | 35 | Thank you for choosing Cleaner! We hope you enjoy using our app as much as we enjoyed creating it for you! -------------------------------------------------------------------------------- /app/src/main/play/listings/en-US/graphics/feature-graphic/play_store_feature_graphic.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/D4rK7355608/com.d4rk.cleaner/68a6c7e23b281de484344db08e29829afb224a92/app/src/main/play/listings/en-US/graphics/feature-graphic/play_store_feature_graphic.png -------------------------------------------------------------------------------- /app/src/main/play/listings/en-US/graphics/ic_launcher-playstore.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/D4rK7355608/com.d4rk.cleaner/68a6c7e23b281de484344db08e29829afb224a92/app/src/main/play/listings/en-US/graphics/ic_launcher-playstore.png -------------------------------------------------------------------------------- /app/src/main/play/listings/en-US/graphics/phone-screenshots/1-screenshot_main.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/D4rK7355608/com.d4rk.cleaner/68a6c7e23b281de484344db08e29829afb224a92/app/src/main/play/listings/en-US/graphics/phone-screenshots/1-screenshot_main.png -------------------------------------------------------------------------------- /app/src/main/play/listings/en-US/graphics/phone-screenshots/2-screenshot_main.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/D4rK7355608/com.d4rk.cleaner/68a6c7e23b281de484344db08e29829afb224a92/app/src/main/play/listings/en-US/graphics/phone-screenshots/2-screenshot_main.png -------------------------------------------------------------------------------- /app/src/main/play/listings/en-US/graphics/phone-screenshots/3-screenshot_main_memory_manager.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/D4rK7355608/com.d4rk.cleaner/68a6c7e23b281de484344db08e29829afb224a92/app/src/main/play/listings/en-US/graphics/phone-screenshots/3-screenshot_main_memory_manager.png -------------------------------------------------------------------------------- /app/src/main/play/listings/en-US/graphics/phone-screenshots/4-screenshot_main_app_manager.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/D4rK7355608/com.d4rk.cleaner/68a6c7e23b281de484344db08e29829afb224a92/app/src/main/play/listings/en-US/graphics/phone-screenshots/4-screenshot_main_app_manager.png -------------------------------------------------------------------------------- /app/src/main/play/listings/en-US/graphics/phone-screenshots/5-screenshot_trash.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/D4rK7355608/com.d4rk.cleaner/68a6c7e23b281de484344db08e29829afb224a92/app/src/main/play/listings/en-US/graphics/phone-screenshots/5-screenshot_trash.png -------------------------------------------------------------------------------- /app/src/main/play/listings/en-US/short-description.txt: -------------------------------------------------------------------------------- 1 | Easy to use one-tap phone cleaner. Say hello to a lightning-fast device! -------------------------------------------------------------------------------- /app/src/main/play/listings/en-US/title.txt: -------------------------------------------------------------------------------- 1 | Cleaner for Android -------------------------------------------------------------------------------- /app/src/main/res/drawable-anydpi/ic_apk_document.xml: -------------------------------------------------------------------------------- 1 | 2 | 8 | 12 | -------------------------------------------------------------------------------- /app/src/main/res/drawable-anydpi/ic_archive_filter.xml: -------------------------------------------------------------------------------- 1 | 2 | 7 | 10 | -------------------------------------------------------------------------------- /app/src/main/res/drawable-anydpi/ic_audio_file.xml: -------------------------------------------------------------------------------- 1 | 2 | 7 | 10 | 11 | -------------------------------------------------------------------------------- /app/src/main/res/drawable-anydpi/ic_image.xml: -------------------------------------------------------------------------------- 1 | 2 | 7 | 8 | 11 | -------------------------------------------------------------------------------- /app/src/main/res/drawable-anydpi/ic_launcher_foreground.xml: -------------------------------------------------------------------------------- 1 | 2 | 7 | 8 | 11 | 14 | 17 | 20 | 23 | 26 | 29 | 32 | 35 | 38 | 41 | -------------------------------------------------------------------------------- /app/src/main/res/drawable-anydpi/ic_shortcut_settings_foreground.xml: -------------------------------------------------------------------------------- 1 | 2 | 9 | 13 | 16 | -------------------------------------------------------------------------------- /app/src/main/res/drawable-anydpi/ic_unknown_document.xml: -------------------------------------------------------------------------------- 1 | 2 | 8 | 12 | -------------------------------------------------------------------------------- /app/src/main/res/drawable-anydpi/ic_video_file.xml: -------------------------------------------------------------------------------- 1 | 2 | 7 | 10 | 11 | -------------------------------------------------------------------------------- /app/src/main/res/drawable-xhdpi/tv_banner.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/D4rK7355608/com.d4rk.cleaner/68a6c7e23b281de484344db08e29829afb224a92/app/src/main/res/drawable-xhdpi/tv_banner.png -------------------------------------------------------------------------------- /app/src/main/res/mipmap-anydpi-v26/ic_launcher.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | -------------------------------------------------------------------------------- /app/src/main/res/mipmap-anydpi-v26/ic_shortcut_settings.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | -------------------------------------------------------------------------------- /app/src/main/res/mipmap-hdpi/ic_launcher.webp: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/D4rK7355608/com.d4rk.cleaner/68a6c7e23b281de484344db08e29829afb224a92/app/src/main/res/mipmap-hdpi/ic_launcher.webp -------------------------------------------------------------------------------- /app/src/main/res/mipmap-hdpi/ic_launcher_round.webp: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/D4rK7355608/com.d4rk.cleaner/68a6c7e23b281de484344db08e29829afb224a92/app/src/main/res/mipmap-hdpi/ic_launcher_round.webp -------------------------------------------------------------------------------- /app/src/main/res/mipmap-ldpi/ic_launcher.webp: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/D4rK7355608/com.d4rk.cleaner/68a6c7e23b281de484344db08e29829afb224a92/app/src/main/res/mipmap-ldpi/ic_launcher.webp -------------------------------------------------------------------------------- /app/src/main/res/mipmap-ldpi/ic_launcher_round.webp: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/D4rK7355608/com.d4rk.cleaner/68a6c7e23b281de484344db08e29829afb224a92/app/src/main/res/mipmap-ldpi/ic_launcher_round.webp -------------------------------------------------------------------------------- /app/src/main/res/mipmap-mdpi/ic_launcher.webp: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/D4rK7355608/com.d4rk.cleaner/68a6c7e23b281de484344db08e29829afb224a92/app/src/main/res/mipmap-mdpi/ic_launcher.webp -------------------------------------------------------------------------------- /app/src/main/res/mipmap-mdpi/ic_launcher_round.webp: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/D4rK7355608/com.d4rk.cleaner/68a6c7e23b281de484344db08e29829afb224a92/app/src/main/res/mipmap-mdpi/ic_launcher_round.webp -------------------------------------------------------------------------------- /app/src/main/res/mipmap-xhdpi/ic_launcher.webp: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/D4rK7355608/com.d4rk.cleaner/68a6c7e23b281de484344db08e29829afb224a92/app/src/main/res/mipmap-xhdpi/ic_launcher.webp -------------------------------------------------------------------------------- /app/src/main/res/mipmap-xhdpi/ic_launcher_round.webp: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/D4rK7355608/com.d4rk.cleaner/68a6c7e23b281de484344db08e29829afb224a92/app/src/main/res/mipmap-xhdpi/ic_launcher_round.webp -------------------------------------------------------------------------------- /app/src/main/res/mipmap-xxhdpi/ic_launcher.webp: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/D4rK7355608/com.d4rk.cleaner/68a6c7e23b281de484344db08e29829afb224a92/app/src/main/res/mipmap-xxhdpi/ic_launcher.webp -------------------------------------------------------------------------------- /app/src/main/res/mipmap-xxhdpi/ic_launcher_round.webp: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/D4rK7355608/com.d4rk.cleaner/68a6c7e23b281de484344db08e29829afb224a92/app/src/main/res/mipmap-xxhdpi/ic_launcher_round.webp -------------------------------------------------------------------------------- /app/src/main/res/mipmap-xxxhdpi/ic_launcher.webp: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/D4rK7355608/com.d4rk.cleaner/68a6c7e23b281de484344db08e29829afb224a92/app/src/main/res/mipmap-xxxhdpi/ic_launcher.webp -------------------------------------------------------------------------------- /app/src/main/res/mipmap-xxxhdpi/ic_launcher_round.webp: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/D4rK7355608/com.d4rk.cleaner/68a6c7e23b281de484344db08e29829afb224a92/app/src/main/res/mipmap-xxxhdpi/ic_launcher_round.webp -------------------------------------------------------------------------------- /app/src/main/res/values-night-v31/colors.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | @android:color/system_neutral1_800 4 | @android:color/system_accent1_100 5 | -------------------------------------------------------------------------------- /app/src/main/res/values-night/colors.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | #2D2D2D 4 | #2E3133 5 | #C1E8FF 6 | -------------------------------------------------------------------------------- /app/src/main/res/values-night/themes.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 9 | -------------------------------------------------------------------------------- /app/src/main/res/values-v31/colors.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | @android:color/system_accent1_100 4 | @android:color/system_neutral1_700 5 | -------------------------------------------------------------------------------- /app/src/main/res/values-v31/themes.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 12 | -------------------------------------------------------------------------------- /app/src/main/res/values/colors.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | #C1E8FF 4 | #454749 5 | #FFFFFF 6 | -------------------------------------------------------------------------------- /app/src/main/res/values/themes.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 10 | 11 | 16 | -------------------------------------------------------------------------------- /app/src/main/res/values/untranslatable_strings.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | Cleaner 4 | Cleaner for Android 5 | Български 6 | English 7 | Français 8 | Magyar 9 | Deutsch 10 | हिन्दी 11 | Bahasa Indonesia 12 | Italiano 13 | 日本語 14 | Polski 15 | Română 16 | Русский 17 | Español 18 | Türkçe 19 | Українська 20 | 中文 21 | ไทย 22 | Português (Brasil) 23 | Copyright ©2020-2025, D4rK 24 | -------------------------------------------------------------------------------- /app/src/main/res/xml-v25/shortcuts.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 8 | 12 | 13 | 14 | -------------------------------------------------------------------------------- /app/src/main/res/xml/config_locales.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | -------------------------------------------------------------------------------- /app/src/main/res/xml/provider_paths.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 6 | 9 | 12 | 15 | -------------------------------------------------------------------------------- /build.gradle.kts: -------------------------------------------------------------------------------- 1 | plugins { 2 | alias(notation= libs.plugins.androidApplication) apply false 3 | alias(notation= libs.plugins.androidLibrary) apply false 4 | alias(notation= libs.plugins.jetbrainsKotlinAndroid) apply false 5 | alias(notation= libs.plugins.compose.compiler) apply false 6 | alias(notation= libs.plugins.googlePlayServices) apply false 7 | alias(notation= libs.plugins.googleFirebase) apply false 8 | alias(notation= libs.plugins.about.libraries) apply true 9 | } -------------------------------------------------------------------------------- /gradle.properties: -------------------------------------------------------------------------------- 1 | # Project-wide Gradle settings. 2 | # IDE (e.g. Android Studio) users: 3 | # Gradle settings configured through the IDE *will override* 4 | # any settings specified in this file. 5 | # For more details on how to configure your build environment visit 6 | # http://www.gradle.org/docs/current/userguide/build_environment.html 7 | # Specifies the JVM arguments used for the daemon process. 8 | # The setting is particularly useful for tweaking memory settings. 9 | org.gradle.jvmargs=-Xmx2048m -Dfile.encoding=UTF-8 10 | # When configured, Gradle will run in incubating parallel mode. 11 | # This option should only be used with decoupled projects. More details, visit 12 | # http://www.gradle.org/docs/current/userguide/multi_project_builds.html#sec:decoupled_projects 13 | # org.gradle.parallel=true 14 | # AndroidX package structure to make it clearer which packages are bundled with the 15 | # Android operating system, and which are packaged with your app"s APK 16 | # https://developer.android.com/topic/libraries/support-library/androidx-rn 17 | android.useAndroidX=true 18 | # Kotlin code style for this project: "official" or "obsolete": 19 | kotlin.code.style=official 20 | # Enables namespacing of each library's R class so that its R class includes only the 21 | # resources declared in the library itself and none from the library's dependencies, 22 | # thereby reducing the size of the R class for that library 23 | android.nonTransitiveRClass=true 24 | android.enableJetifier=true -------------------------------------------------------------------------------- /gradle/libs.versions.toml: -------------------------------------------------------------------------------- 1 | [versions] 2 | agp = "8.8.1" 3 | coilVideoVersion = "3.1.0" 4 | compressor = "3.0.1" 5 | aboutlibraries = "11.3.0" 6 | constraintlayoutCompose = "1.1.0" 7 | kotlin = "2.1.0" 8 | google-services = "4.4.2" 9 | google-firebase-crashlytics = "3.0.3" 10 | 11 | [libraries] 12 | androidx-constraintlayout-compose = { module = "androidx.constraintlayout:constraintlayout-compose", version.ref = "constraintlayoutCompose" } 13 | coil3-coil-video = { module = "io.coil-kt.coil3:coil-video", version.ref = "coilVideoVersion" } 14 | compressor = { module = "id.zelory:compressor", version.ref = "compressor" } 15 | 16 | [plugins] 17 | androidApplication = { id = "com.android.application", version.ref = "agp" } 18 | androidLibrary = { id = "com.android.library", version.ref = "agp" } 19 | jetbrainsKotlinAndroid = { id = "org.jetbrains.kotlin.android", version.ref = "kotlin" } 20 | googlePlayServices = { id = "com.google.gms.google-services", version.ref = "google-services" } 21 | googleFirebase = { id = "com.google.firebase.crashlytics", version.ref = "google-firebase-crashlytics" } 22 | compose-compiler = { id = "org.jetbrains.kotlin.plugin.compose", version.ref = "kotlin" } 23 | about-libraries = { id = "com.mikepenz.aboutlibraries.plugin", version.ref = "aboutlibraries" } -------------------------------------------------------------------------------- /gradle/wrapper/gradle-wrapper.jar: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/D4rK7355608/com.d4rk.cleaner/68a6c7e23b281de484344db08e29829afb224a92/gradle/wrapper/gradle-wrapper.jar -------------------------------------------------------------------------------- /gradle/wrapper/gradle-wrapper.properties: -------------------------------------------------------------------------------- 1 | #Tue Feb 25 20:41:53 EET 2025 2 | distributionBase=GRADLE_USER_HOME 3 | distributionPath=wrapper/dists 4 | distributionUrl=https\://services.gradle.org/distributions/gradle-8.13-bin.zip 5 | zipStoreBase=GRADLE_USER_HOME 6 | zipStorePath=wrapper/dists 7 | -------------------------------------------------------------------------------- /gradlew.bat: -------------------------------------------------------------------------------- 1 | @rem 2 | @rem Copyright 2015 the original author or authors. 3 | @rem 4 | @rem Licensed under the Apache License, Version 2.0 (the "License"); 5 | @rem you may not use this file except in compliance with the License. 6 | @rem You may obtain a copy of the License at 7 | @rem 8 | @rem https://www.apache.org/licenses/LICENSE-2.0 9 | @rem 10 | @rem Unless required by applicable law or agreed to in writing, software 11 | @rem distributed under the License is distributed on an "AS IS" BASIS, 12 | @rem WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | @rem See the License for the specific language governing permissions and 14 | @rem limitations under the License. 15 | @rem 16 | 17 | @if "%DEBUG%" == "" @echo off 18 | @rem ########################################################################## 19 | @rem 20 | @rem Gradle startup script for Windows 21 | @rem 22 | @rem ########################################################################## 23 | 24 | @rem Set local scope for the variables with windows NT shell 25 | if "%OS%"=="Windows_NT" setlocal 26 | 27 | set DIRNAME=%~dp0 28 | if "%DIRNAME%" == "" set DIRNAME=. 29 | set APP_BASE_NAME=%~n0 30 | set APP_HOME=%DIRNAME% 31 | 32 | @rem Resolve any "." and ".." in APP_HOME to make it shorter. 33 | for %%i in ("%APP_HOME%") do set APP_HOME=%%~fi 34 | 35 | @rem Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script. 36 | set DEFAULT_JVM_OPTS="-Xmx64m" "-Xms64m" 37 | 38 | @rem Find java.exe 39 | if defined JAVA_HOME goto findJavaFromJavaHome 40 | 41 | set JAVA_EXE=java.exe 42 | %JAVA_EXE% -version >NUL 2>&1 43 | if "%ERRORLEVEL%" == "0" goto execute 44 | 45 | echo. 46 | echo ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH. 47 | echo. 48 | echo Please set the JAVA_HOME variable in your environment to match the 49 | echo location of your Java installation. 50 | 51 | goto fail 52 | 53 | :findJavaFromJavaHome 54 | set JAVA_HOME=%JAVA_HOME:"=% 55 | set JAVA_EXE=%JAVA_HOME%/bin/java.exe 56 | 57 | if exist "%JAVA_EXE%" goto execute 58 | 59 | echo. 60 | echo ERROR: JAVA_HOME is set to an invalid directory: %JAVA_HOME% 61 | echo. 62 | echo Please set the JAVA_HOME variable in your environment to match the 63 | echo location of your Java installation. 64 | 65 | goto fail 66 | 67 | :execute 68 | @rem Setup the command line 69 | 70 | set CLASSPATH=%APP_HOME%\gradle\wrapper\gradle-wrapper.jar 71 | 72 | 73 | @rem Execute Gradle 74 | "%JAVA_EXE%" %DEFAULT_JVM_OPTS% %JAVA_OPTS% %GRADLE_OPTS% "-Dorg.gradle.appname=%APP_BASE_NAME%" -classpath "%CLASSPATH%" org.gradle.wrapper.GradleWrapperMain %* 75 | 76 | :end 77 | @rem End local scope for the variables with windows NT shell 78 | if "%ERRORLEVEL%"=="0" goto mainEnd 79 | 80 | :fail 81 | rem Set variable GRADLE_EXIT_CONSOLE if you need the _script_ return code instead of 82 | rem the _cmd.exe /c_ return code! 83 | if not "" == "%GRADLE_EXIT_CONSOLE%" exit 1 84 | exit /b 1 85 | 86 | :mainEnd 87 | if "%OS%"=="Windows_NT" endlocal 88 | 89 | :omega 90 | -------------------------------------------------------------------------------- /settings.gradle.kts: -------------------------------------------------------------------------------- 1 | pluginManagement { 2 | repositories { 3 | gradlePluginPortal() 4 | google() 5 | mavenCentral() 6 | maven { 7 | setUrl("https://jitpack.io") 8 | } 9 | } 10 | } 11 | 12 | @Suppress("UnstableApiUsage") dependencyResolutionManagement { 13 | repositoriesMode.set(RepositoriesMode.FAIL_ON_PROJECT_REPOS) 14 | repositories { 15 | google() 16 | mavenCentral() 17 | maven { 18 | setUrl("https://jitpack.io") 19 | } 20 | } 21 | } 22 | 23 | rootProject.name = "Cleaner for Android" 24 | include(":app") --------------------------------------------------------------------------------