├── .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 |
5 |
6 |
7 |
8 |
9 |
10 |
11 |
12 |
13 |
14 |
15 |
16 |
17 |
18 |
19 |
20 |
21 |
22 |
23 |
24 |
25 |
26 |
27 |
28 |
29 |
30 |
31 |
32 |
33 |
34 |
35 |
36 |
37 |
38 |
39 |
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 |
7 |
8 |
9 |
10 |
11 |
12 |
13 |
14 |
15 |
16 |
17 |
18 |
--------------------------------------------------------------------------------
/.idea/gradle.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
8 |
9 |
10 |
11 |
12 |
13 |
14 |
15 |
16 |
17 |
18 |
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 |
4 |
5 |
6 |
7 |
8 |
9 |
10 |
11 |
12 |
13 |
14 |
15 |
16 |
17 |
18 |
19 |
20 |
21 |
22 |
23 |
24 |
25 |
26 |
27 |
28 |
29 |
30 |
31 |
32 |
33 |
34 |
35 |
36 |
37 |
38 |
39 |
40 |
41 |
42 |
43 |
44 |
45 |
46 |
47 |
48 |
49 |
50 |
51 |
52 |
53 |
54 |
55 |
56 |
57 |
58 |
59 |
60 |
61 |
62 |
63 |
64 |
65 |
66 |
67 |
68 |
69 |
70 |
--------------------------------------------------------------------------------
/.idea/kotlinc.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
--------------------------------------------------------------------------------
/.idea/migrations.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
8 |
9 |
10 |
--------------------------------------------------------------------------------
/.idea/misc.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
8 |
9 |
10 |
11 |
12 |
13 |
14 |
15 |
--------------------------------------------------------------------------------
/.idea/studiobot.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
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 | 
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 | 
--------------------------------------------------------------------------------
/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")
--------------------------------------------------------------------------------