├── .github
├── FUNDING.yml
├── ISSUE_TEMPLATE
│ ├── bug_report.yml
│ └── feature_request.yml
└── workflows
│ ├── debug.yml
│ └── release.yml
├── .gitignore
├── .idea
├── .gitignore
├── .name
├── AndroidProjectSystem.xml
├── codeStyles
│ ├── Project.xml
│ └── codeStyleConfig.xml
├── compiler.xml
├── deploymentTargetSelector.xml
├── gradle.xml
├── kotlinc.xml
├── migrations.xml
├── misc.xml
├── runConfigurations.xml
└── vcs.xml
├── LICENSE
├── Readme.md
├── app
├── .gitignore
├── build.gradle.kts
├── proguard-rules.pro
└── src
│ ├── androidTest
│ └── java
│ │ └── nethical
│ │ └── digipaws
│ │ └── ExampleInstrumentedTest.kt
│ ├── main
│ ├── AndroidManifest.xml
│ ├── java
│ │ └── nethical
│ │ │ └── digipaws
│ │ │ ├── Constants.kt
│ │ │ ├── CrashLogger.kt
│ │ │ ├── Digipaws.kt
│ │ │ ├── blockers
│ │ │ ├── AppBlocker.kt
│ │ │ ├── BaseBlocker.kt
│ │ │ ├── FocusModeBlocker.kt
│ │ │ ├── KeywordBlocker.kt
│ │ │ └── ViewBlocker.kt
│ │ │ ├── data
│ │ │ └── blockers
│ │ │ │ ├── KeywordPacks.kt
│ │ │ │ └── PackageWand.kt
│ │ │ ├── receivers
│ │ │ └── AdminReceiver.kt
│ │ │ ├── services
│ │ │ ├── AppBlockerService.kt
│ │ │ ├── BaseBlockingService.kt
│ │ │ ├── GeneralFeaturesService.kt
│ │ │ ├── KeywordBlockerService.kt
│ │ │ ├── UsageTrackingService.kt
│ │ │ └── ViewBlockerService.kt
│ │ │ ├── ui
│ │ │ ├── activity
│ │ │ │ ├── FragmentActivity.kt
│ │ │ │ ├── LauncherActivity.kt
│ │ │ │ ├── MainActivity.kt
│ │ │ │ ├── ManageKeywordsActivity.kt
│ │ │ │ ├── SelectAppsActivity.kt
│ │ │ │ ├── ShortcutActivity.kt
│ │ │ │ ├── TimedActionActivity.kt
│ │ │ │ ├── UsageMetricsActivity.kt
│ │ │ │ └── WarningActivity.kt
│ │ │ ├── dialogs
│ │ │ │ ├── BaseDialog.kt
│ │ │ │ ├── StartFocusMode.kt
│ │ │ │ ├── TweakAppBlockerWarning.kt
│ │ │ │ ├── TweakGrayScaleMode.kt
│ │ │ │ ├── TweakKeywordBlocker.kt
│ │ │ │ ├── TweakKeywordPack.kt
│ │ │ │ ├── TweakUsageTracker.kt
│ │ │ │ ├── TweakViewBlockerCheatHours.kt
│ │ │ │ └── TweakViewBlockerWarning.kt
│ │ │ ├── fragments
│ │ │ │ ├── anti_uninstall
│ │ │ │ │ ├── ChooseModeFragment.kt
│ │ │ │ │ ├── SetupPasswordModeFragment.kt
│ │ │ │ │ └── SetupTimedModeFragment.kt
│ │ │ │ ├── installation
│ │ │ │ │ ├── AccessibilityGuide.kt
│ │ │ │ │ ├── PermissionsFragment.kt
│ │ │ │ │ └── WelcomeFragment.kt
│ │ │ │ └── usage
│ │ │ │ │ ├── AllAppsUsageFragment.kt
│ │ │ │ │ └── AppUsageBreakdown.kt
│ │ │ ├── overlay
│ │ │ │ └── UsageStatOverlayManager.kt
│ │ │ └── widgets
│ │ │ │ ├── ReelsWidgetProvider.kt
│ │ │ │ └── ScreentimeWidgetProvider.kt
│ │ │ ├── utils
│ │ │ ├── AnimTools.kt
│ │ │ ├── BackupTools.kt
│ │ │ ├── CommonUtils.kt
│ │ │ ├── MonochromeTools.kt
│ │ │ ├── NotificationTimerManager.kt
│ │ │ ├── SavedPreferencesLoader.kt
│ │ │ ├── ShizukuRunner.kt
│ │ │ ├── TimeTools.kt
│ │ │ └── UsageStatsHelper.kt
│ │ │ └── views
│ │ │ ├── CustomMarkerView.kt
│ │ │ └── HorizontalNumberPicker.kt
│ └── res
│ │ ├── anim
│ │ ├── fade_in.xml
│ │ └── fade_out.xml
│ │ ├── drawable
│ │ ├── baseline_add_24.xml
│ │ ├── baseline_android_24.xml
│ │ ├── baseline_app_shortcut_24.xml
│ │ ├── baseline_auto_fix_high_24.xml
│ │ ├── baseline_close_24.xml
│ │ ├── baseline_date_range_24.xml
│ │ ├── baseline_delete_24.xml
│ │ ├── baseline_done_24.xml
│ │ ├── baseline_edit_24.xml
│ │ ├── baseline_help_outline_24.xml
│ │ ├── baseline_info_24.xml
│ │ ├── baseline_lock_24.xml
│ │ ├── baseline_query_stats_24.xml
│ │ ├── baseline_read_more_24.xml
│ │ ├── baseline_refresh_24.xml
│ │ ├── baseline_remove_24.xml
│ │ ├── baseline_share_24.xml
│ │ ├── baseline_start_24.xml
│ │ ├── baseline_stop_24.xml
│ │ ├── baseline_task_alt_24.xml
│ │ ├── baseline_warning_24.xml
│ │ ├── focus_mode_icon.jpg
│ │ ├── ic_launcher_background.xml
│ │ ├── ic_launcher_foreground.xml
│ │ ├── ic_launcher_monochrome.xml
│ │ └── widget_background.xml
│ │ ├── layout
│ │ ├── activity_add_timed_action_activity.xml
│ │ ├── activity_fragment.xml
│ │ ├── activity_launcher.xml
│ │ ├── activity_main.xml
│ │ ├── activity_manage_keywords.xml
│ │ ├── activity_select_apps.xml
│ │ ├── activity_usage_metrics.xml
│ │ ├── app_usage_item.xml
│ │ ├── cheat_hour_item.xml
│ │ ├── custom_marker_view.xml
│ │ ├── dialog_add_keyword.xml
│ │ ├── dialog_add_timed_action.xml
│ │ ├── dialog_config_tracker.xml
│ │ ├── dialog_focus_mode.xml
│ │ ├── dialog_grayscale.xml
│ │ ├── dialog_keyword_blocker_config.xml
│ │ ├── dialog_keyword_package.xml
│ │ ├── dialog_permission_info.xml
│ │ ├── dialog_remove_anti_uninstall.xml
│ │ ├── dialog_tweak_blocker_warning.xml
│ │ ├── dialog_warning_overlay.xml
│ │ ├── fragment_accessibility_guide.xml
│ │ ├── fragment_all_app_usage.xml
│ │ ├── fragment_app_usage_breakdown.xml
│ │ ├── fragment_chose_anti_uninstall_mode.xml
│ │ ├── fragment_permissions.xml
│ │ ├── fragment_setup_password_mode.xml
│ │ ├── fragment_setup_timed_mode.xml
│ │ ├── fragment_welcome.xml
│ │ ├── horizontal_number_picker.xml
│ │ ├── keyword_item.xml
│ │ ├── launcher_item.xml
│ │ ├── overlay_usage_stat.xml
│ │ ├── reel_stats.xml
│ │ ├── select_apps_item.xml
│ │ ├── widget_app_stats.xml
│ │ └── widget_reels_count.xml
│ │ ├── menu
│ │ ├── app_wand.xml
│ │ └── usage_tracker_options.xml
│ │ ├── mipmap-anydpi-v26
│ │ ├── ic_launcher.xml
│ │ └── ic_launcher_round.xml
│ │ ├── mipmap-hdpi
│ │ ├── ic_launcher.webp
│ │ └── ic_launcher_round.webp
│ │ ├── mipmap-mdpi
│ │ ├── ic_launcher.webp
│ │ └── ic_launcher_round.webp
│ │ ├── mipmap-xhdpi
│ │ ├── ic_launcher.webp
│ │ └── ic_launcher_round.webp
│ │ ├── mipmap-xxhdpi
│ │ ├── ic_launcher.webp
│ │ └── ic_launcher_round.webp
│ │ ├── mipmap-xxxhdpi
│ │ ├── ic_launcher.webp
│ │ └── ic_launcher_round.webp
│ │ ├── values-de
│ │ └── strings.xml
│ │ ├── values-fa
│ │ └── strings.xml
│ │ ├── values-fr
│ │ └── strings.xml
│ │ ├── values-hi
│ │ └── strings.xml
│ │ ├── values-night
│ │ ├── colors.xml
│ │ └── themes.xml
│ │ ├── values-pt-rBR
│ │ └── strings.xml
│ │ ├── values
│ │ ├── colors.xml
│ │ ├── strings.xml
│ │ └── themes.xml
│ │ └── xml
│ │ ├── app_blocker_service_config.xml
│ │ ├── device_admin_receiver_info.xml
│ │ ├── file_paths.xml
│ │ ├── general_features_service_config.xml
│ │ ├── keyword_blocker_service_config.xml
│ │ ├── shortcuts.xml
│ │ ├── usage_tracker_service_config.xml
│ │ ├── view_blocker_service_config.xml
│ │ ├── widget_reels_metadata.xml
│ │ └── widget_screentime_metadata.xml
│ └── test
│ └── java
│ └── nethical
│ └── digipaws
│ └── ExampleUnitTest.kt
├── build.gradle.kts
├── fastlane
└── metadata
│ └── android
│ └── en-US
│ ├── full_description.txt
│ ├── images
│ ├── featureGraphic.png
│ ├── icon.png
│ └── phoneScreenshots
│ │ ├── 1.png
│ │ ├── 2.png
│ │ ├── 3.png
│ │ ├── 4.png
│ │ ├── 5.png
│ │ ├── 6.png
│ │ ├── 7.png
│ │ └── 8.png
│ ├── short_description.txt
│ └── title.txt
├── gradle.properties
├── gradle
├── libs.versions.toml
└── wrapper
│ ├── gradle-wrapper.jar
│ └── gradle-wrapper.properties
├── gradlew
├── gradlew.bat
├── local
└── settings.gradle.kts
/.github/FUNDING.yml:
--------------------------------------------------------------------------------
1 | # These are supported funding model platforms
2 |
3 | github: # Replace with up to 4 GitHub Sponsors-enabled usernames e.g., [user1, user2]
4 | patreon: nethical
5 | open_collective: # Replace with a single Open Collective username
6 | ko_fi: # Replace with a single Ko-fi username
7 | tidelift: # Replace with a single Tidelift platform-name/package-name e.g., npm/babel
8 | community_bridge: # Replace with a single Community Bridge project-name e.g., cloud-foundry
9 | liberapay: # Replace with a single Liberapay username
10 | issuehunt: # Replace with a single IssueHunt username
11 | lfx_crowdfunding: # Replace with a single LFX Crowdfunding project-name e.g., cloud-foundry
12 | polar: # Replace with a single Polar username
13 | buy_me_a_coffee: # Replace with a single Buy Me a Coffee username
14 | custom: # Replace with up to 4 custom sponsorship URLs e.g., ['link1', 'link2']
15 |
--------------------------------------------------------------------------------
/.github/ISSUE_TEMPLATE/bug_report.yml:
--------------------------------------------------------------------------------
1 | name: Bug Report
2 | description: Report a reproducible bug or unexpected behavior in DigiPaws.
3 | title: "[Bug]: "
4 | labels: ["bug"]
5 | assignees: []
6 |
7 | body:
8 | - type: markdown
9 | attributes:
10 | value: |
11 | Thank you for reporting an issue. Please provide as much detail as possible to help us investigate.
12 |
13 | - type: input
14 | id: summary
15 | attributes:
16 | label: Summary
17 | description: A short description of the issue.
18 | placeholder: e.g., "Focus Mode timer does not start on Android 14."
19 |
20 | - type: textarea
21 | id: steps
22 | attributes:
23 | label: Steps to Reproduce
24 | description: Clear steps to reproduce the issue.
25 | placeholder: |
26 | 1. Go to Focus Mode
27 | 2. Set timer
28 | 3. Observe that timer does not start
29 |
30 | - type: textarea
31 | id: expected
32 | attributes:
33 | label: Expected Behavior
34 | description: Describe what you expected to happen.
35 |
36 | - type: textarea
37 | id: actual
38 | attributes:
39 | label: Actual Behavior
40 | description: Describe what actually happened.
41 |
42 | - type: input
43 | id: environment
44 | attributes:
45 | label: Environment
46 | description: Device model, OS version, DigiPaws version.
47 | placeholder: e.g., OnePlus 9, Android 14, DigiPaws 1.2.1
48 |
49 | - type: textarea
50 | id: additional
51 | attributes:
52 | label: Additional Context (optional)
53 | description: Add logs, screenshots, or any other helpful information.
54 |
--------------------------------------------------------------------------------
/.github/ISSUE_TEMPLATE/feature_request.yml:
--------------------------------------------------------------------------------
1 | name: Feature Request
2 | description: Suggest a new feature or improvement for DigiPaws.
3 | title: "[Feature]: "
4 | labels: ["enhancement"]
5 | assignees: []
6 |
7 | body:
8 | - type: markdown
9 | attributes:
10 | value: |
11 | Thank you for suggesting a feature. Please describe it clearly and explain the problem it would solve.
12 |
13 | - type: input
14 | id: feature
15 | attributes:
16 | label: Feature Summary
17 | description: A clear and concise summary of the feature.
18 | placeholder: e.g., "Add challenge mode that unlocks apps after completing a quiz."
19 |
20 | - type: textarea
21 | id: problem
22 | attributes:
23 | label: Problem Statement
24 | description: Describe the problem this feature would address.
25 |
26 | - type: textarea
27 | id: solution
28 | attributes:
29 | label: Proposed Solution
30 | description: Describe how you would like the feature to work.
31 |
32 | - type: input
33 | id: user-impact
34 | attributes:
35 | label: Target Users
36 | description: Who will benefit from this feature?
37 |
38 | - type: textarea
39 | id: additional
40 | attributes:
41 | label: Additional Context (optional)
42 | description: Any other information, mockups, or references.
43 |
--------------------------------------------------------------------------------
/.github/workflows/debug.yml:
--------------------------------------------------------------------------------
1 | name: Debug CI
2 |
3 | on:
4 | workflow_dispatch: # Manual trigger
5 |
6 | jobs:
7 | build:
8 |
9 | runs-on: ubuntu-latest
10 |
11 | steps:
12 | - name: Checkout repository
13 | uses: actions/checkout@v4
14 |
15 | - name: Set up JDK 17
16 | uses: actions/setup-java@v4.2.1
17 | with:
18 | distribution: 'temurin'
19 | java-version: '17'
20 |
21 | - name: Give permission to executable
22 | run: chmod +x gradlew
23 |
24 | - name: Clear build cache
25 | uses: gradle/gradle-build-action@v3.1.0
26 | with:
27 | gradle-version: nightly
28 | arguments: clean
29 |
30 | - name: Build Lite Debug APK
31 | run: ./gradlew assembleLiteDebug
32 |
33 | - name: Upload Lite Debug APK
34 | uses: actions/upload-artifact@v4
35 | with:
36 | name: lite-debug-apk
37 | path: app/build/outputs/apk/lite/debug
38 |
--------------------------------------------------------------------------------
/.gitignore:
--------------------------------------------------------------------------------
1 | *.iml
2 | .gradle
3 | /local.properties
4 | /.idea/caches
5 | /.idea/libraries
6 | /.idea/modules.xml
7 | /.idea/workspace.xml
8 | /.idea/navEditor.xml
9 | /.idea/assetWizardSettings.xml
10 | .DS_Store
11 | /build
12 | /captures
13 | .externalNativeBuild
14 | .cxx
15 | local.properties
16 |
--------------------------------------------------------------------------------
/.idea/.gitignore:
--------------------------------------------------------------------------------
1 | # Default ignored files
2 | /shelf/
3 | /workspace.xml
4 |
--------------------------------------------------------------------------------
/.idea/.name:
--------------------------------------------------------------------------------
1 | DigiPaws
--------------------------------------------------------------------------------
/.idea/AndroidProjectSystem.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
--------------------------------------------------------------------------------
/.idea/codeStyles/Project.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
8 |
9 |
10 |
11 |
12 |
13 |
14 |
15 |
16 |
17 | xmlns:android
18 |
19 | ^$
20 |
21 |
22 |
23 |
24 |
25 |
26 |
27 |
28 | xmlns:.*
29 |
30 | ^$
31 |
32 |
33 | BY_NAME
34 |
35 |
36 |
37 |
38 |
39 |
40 | .*:id
41 |
42 | http://schemas.android.com/apk/res/android
43 |
44 |
45 |
46 |
47 |
48 |
49 |
50 |
51 | .*:name
52 |
53 | http://schemas.android.com/apk/res/android
54 |
55 |
56 |
57 |
58 |
59 |
60 |
61 |
62 | name
63 |
64 | ^$
65 |
66 |
67 |
68 |
69 |
70 |
71 |
72 |
73 | style
74 |
75 | ^$
76 |
77 |
78 |
79 |
80 |
81 |
82 |
83 |
84 | .*
85 |
86 | ^$
87 |
88 |
89 | BY_NAME
90 |
91 |
92 |
93 |
94 |
95 |
96 | .*
97 |
98 | http://schemas.android.com/apk/res/android
99 |
100 |
101 | ANDROID_ATTRIBUTE_ORDER
102 |
103 |
104 |
105 |
106 |
107 |
108 | .*
109 |
110 | .*
111 |
112 |
113 | BY_NAME
114 |
115 |
116 |
117 |
118 |
119 |
120 |
121 |
122 |
123 |
--------------------------------------------------------------------------------
/.idea/codeStyles/codeStyleConfig.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
--------------------------------------------------------------------------------
/.idea/compiler.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
--------------------------------------------------------------------------------
/.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 |
19 |
20 |
--------------------------------------------------------------------------------
/.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 |
--------------------------------------------------------------------------------
/.idea/runConfigurations.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
8 |
9 |
10 |
11 |
12 |
13 |
14 |
15 |
16 |
17 |
--------------------------------------------------------------------------------
/.idea/vcs.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
--------------------------------------------------------------------------------
/app/.gitignore:
--------------------------------------------------------------------------------
1 | /build
--------------------------------------------------------------------------------
/app/build.gradle.kts:
--------------------------------------------------------------------------------
1 | plugins {
2 | alias(libs.plugins.android.application)
3 | alias(libs.plugins.kotlin.android)
4 | }
5 |
6 | android {
7 | namespace = "nethical.digipaws"
8 | compileSdk = 34
9 | flavorDimensions += "version"
10 |
11 | defaultConfig {
12 | applicationId = "nethical.digipaws"
13 | minSdk = 26
14 | targetSdk = 34
15 | versionCode = 23
16 | versionName = "2.3-alpha"
17 | testInstrumentationRunner = "androidx.test.runner.AndroidJUnitRunner"
18 | }
19 |
20 | productFlavors {
21 | create("lite") {
22 | dimension = "version"
23 | versionNameSuffix = "-lite"
24 | buildConfigField("Boolean", "FDROID_VARIANT", "true")
25 | }
26 |
27 | create("play-store") {
28 | dimension = "version"
29 | versionNameSuffix = "-full"
30 | buildConfigField("Boolean", "FDROID_VARIANT", "false")
31 | }
32 | }
33 |
34 |
35 | splits {
36 | abi {
37 | isEnable = false
38 |
39 | }
40 | }
41 |
42 | buildTypes {
43 | release {
44 | isMinifyEnabled = false
45 | proguardFiles(
46 | getDefaultProguardFile("proguard-android-optimize.txt"),
47 | "proguard-rules.pro"
48 | )
49 |
50 | // required because of hardcoded f-droid values
51 | applicationVariants.all {
52 | val variant = this
53 | if (variant.flavorName == "lite") {
54 | variant.outputs
55 | .map { it as com.android.build.gradle.internal.api.BaseVariantOutputImpl }
56 | .forEach { output ->
57 | val outputFileName = "app-lite-universal-release-unsigned.apk"
58 | println("OutputFileName: $outputFileName")
59 | output.outputFileName = outputFileName
60 | }
61 | }
62 | }
63 | }
64 | }
65 | compileOptions {
66 | sourceCompatibility = JavaVersion.VERSION_1_8
67 | targetCompatibility = JavaVersion.VERSION_1_8
68 | }
69 | kotlinOptions {
70 | jvmTarget = "1.8"
71 | }
72 | buildFeatures {
73 | viewBinding = true
74 | buildConfig = true
75 | }
76 | }
77 |
78 |
79 |
80 | dependencies {
81 |
82 | implementation(libs.androidx.core.ktx)
83 | implementation(libs.androidx.appcompat)
84 | implementation(libs.material)
85 | implementation(libs.androidx.activity)
86 | implementation(libs.androidx.constraintlayout)
87 |
88 | // Shizuku dependecies
89 | implementation (libs.api)
90 | implementation (libs.provider)
91 |
92 | implementation(libs.gson)
93 |
94 | testImplementation(libs.junit)
95 | androidTestImplementation(libs.androidx.junit)
96 | androidTestImplementation(libs.androidx.espresso.core)
97 |
98 | implementation(libs.mpandroidchart)
99 | implementation(libs.timerangepicker)
100 |
101 | }
--------------------------------------------------------------------------------
/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/androidTest/java/nethical/digipaws/ExampleInstrumentedTest.kt:
--------------------------------------------------------------------------------
1 | package nethical.digipaws
2 |
3 | import androidx.test.platform.app.InstrumentationRegistry
4 | import androidx.test.ext.junit.runners.AndroidJUnit4
5 |
6 | import org.junit.Test
7 | import org.junit.runner.RunWith
8 |
9 | import org.junit.Assert.*
10 |
11 | /**
12 | * Instrumented test, which will execute on an Android device.
13 | *
14 | * See [testing documentation](http://d.android.com/tools/testing).
15 | */
16 | @RunWith(AndroidJUnit4::class)
17 | class ExampleInstrumentedTest {
18 | @Test
19 | fun useAppContext() {
20 | // Context of the app under test.
21 | val appContext = InstrumentationRegistry.getInstrumentation().targetContext
22 | assertEquals("nethical.digipaws", appContext.packageName)
23 | }
24 | }
--------------------------------------------------------------------------------
/app/src/main/java/nethical/digipaws/Constants.kt:
--------------------------------------------------------------------------------
1 | package nethical.digipaws
2 |
3 | class Constants {
4 | companion object {
5 | // available modes for setting up anti-uninstall
6 | const val ANTI_UNINSTALL_PASSWORD_MODE = 1
7 | const val ANTI_UNINSTALL_TIMED_MODE = 2
8 |
9 | // available types of warning screen
10 | const val WARNING_SCREEN_MODE_VIEW_BLOCKER = 1
11 | const val WARNING_SCREEN_MODE_APP_BLOCKER = 2
12 |
13 | // available types for focus mode
14 | const val FOCUS_MODE_BLOCK_ALL_EX_SELECTED = 1
15 | const val FOCUS_MODE_BLOCK_SELECTED = 2
16 |
17 | // available types for focus mode
18 | const val GRAYSCALE_MODE_ALL = 1 // apply to all apps
19 | const val GRAYSCALE_MODE_ONLY_SELECTED = 2 // apply to only selected
20 | const val GRAYSCALE_MODE_ALL_EXCEPT_SELECTED = 3 // apply to all except selected
21 | const val GRAYSCALE_MODE_OFF = 4 // turned off
22 | }
23 | }
--------------------------------------------------------------------------------
/app/src/main/java/nethical/digipaws/CrashLogger.kt:
--------------------------------------------------------------------------------
1 | package nethical.digipaws
2 |
3 | import android.content.Context
4 | import android.os.Build
5 | import java.io.File
6 | import java.io.FileWriter
7 | import java.io.PrintWriter
8 | import java.text.SimpleDateFormat
9 | import java.util.Date
10 | import java.util.Locale
11 |
12 | class CrashLogger(private val context: Context) : Thread.UncaughtExceptionHandler {
13 |
14 | private val logFile = File(context.filesDir, "crash_log.txt")
15 | private val defaultHandler = Thread.getDefaultUncaughtExceptionHandler()
16 |
17 | override fun uncaughtException(thread: Thread, throwable: Throwable) {
18 | try {
19 | val timeStamp =
20 | SimpleDateFormat("yyyy-MM-dd HH:mm:ss", Locale.getDefault()).format(Date())
21 | val writer = FileWriter(logFile, true) // Append mode
22 | writer.append("\n--- Crash at $timeStamp ---\n")
23 | writer.append("Device: ${Build.MANUFACTURER} ${Build.MODEL} (Android ${Build.VERSION.RELEASE})\n")
24 | val printWriter = PrintWriter(writer)
25 | throwable.printStackTrace(printWriter)
26 | printWriter.flush()
27 | printWriter.close()
28 | } catch (e: Exception) {
29 | e.printStackTrace()
30 | }
31 |
32 | defaultHandler?.uncaughtException(thread, throwable) // Let the system handle the crash
33 | }
34 | }
35 |
--------------------------------------------------------------------------------
/app/src/main/java/nethical/digipaws/Digipaws.kt:
--------------------------------------------------------------------------------
1 | package nethical.digipaws
2 |
3 | import android.app.Application
4 | import com.google.android.material.color.DynamicColors
5 |
6 | class Digipaws: Application() {
7 | override fun onCreate() {
8 | DynamicColors.applyToActivitiesIfAvailable(this)
9 | Thread.setDefaultUncaughtExceptionHandler(CrashLogger(this))
10 | super.onCreate()
11 | }
12 | }
13 |
--------------------------------------------------------------------------------
/app/src/main/java/nethical/digipaws/blockers/BaseBlocker.kt:
--------------------------------------------------------------------------------
1 | package nethical.digipaws.blockers
2 |
3 | import android.os.SystemClock
4 |
5 |
6 | abstract class BaseBlocker{
7 | fun isDelayOver(lastTimeStamp: Float): Boolean {
8 | val currentTime = SystemClock.uptimeMillis().toFloat()
9 | return currentTime - lastTimeStamp > 30000
10 | }
11 |
12 | fun isDelayOver(lastTimeStamp: Float, delay: Int): Boolean {
13 | val currentTime = SystemClock.uptimeMillis().toFloat()
14 | return currentTime - lastTimeStamp > delay
15 | }
16 | }
--------------------------------------------------------------------------------
/app/src/main/java/nethical/digipaws/blockers/ViewBlocker.kt:
--------------------------------------------------------------------------------
1 | package nethical.digipaws.blockers
2 |
3 | import android.os.SystemClock
4 | import android.view.accessibility.AccessibilityNodeInfo
5 | import nethical.digipaws.utils.TimeTools
6 | import java.util.Calendar
7 |
8 | class ViewBlocker : BaseBlocker() {
9 | companion object {
10 | fun findElementById(node: AccessibilityNodeInfo?, id: String?): AccessibilityNodeInfo? {
11 | if (node == null) return null
12 | var targetNode: AccessibilityNodeInfo? = null
13 | try {
14 | targetNode = node.findAccessibilityNodeInfosByViewId(id!!)[0]
15 | } catch (e: Exception) {
16 | // e.printStackTrace();
17 | }
18 | return targetNode
19 | }
20 |
21 | val TIKTOK_PACKAGE_NAMES = hashSetOf(
22 | "com.ss.android.ugc.trill",
23 | "com.zhiliaoapp.musically",
24 | "com.ss.android.ugc.aweme"
25 | )
26 | val BLOCKED_VIEW_ID_LIST = mutableListOf(
27 | "com.instagram.android:id/root_clips_layout",
28 | "com.google.android.youtube:id/reel_recycler",
29 | "app.revanced.android.youtube:id/reel_recycler"
30 | )
31 |
32 | }
33 | private val cooldownViewIdsList = mutableMapOf()
34 |
35 |
36 | var isIGInboxReelAllowed = false
37 | var isFirstReelInFeedAllowed = false
38 |
39 | var cheatMinuteStartTime: Int? = null
40 | var cheatMinutesEndTIme: Int? = null
41 |
42 | fun doesViewNeedToBeBlocked(
43 | node: AccessibilityNodeInfo,
44 | packageName: String
45 | ): ViewBlockerResult? {
46 | if (isCheatHourActive()) {
47 | return null
48 | }
49 | if (TIKTOK_PACKAGE_NAMES.contains(packageName)) {
50 | if (isCooldownActive(packageName)) {
51 | return ViewBlockerResult(
52 | isReelFoundInCooldownState = true,
53 | viewId = packageName,
54 | requestHomePressInstead = true
55 | )
56 | }
57 | return ViewBlockerResult(
58 | isBlocked = true,
59 | viewId = packageName,
60 | requestHomePressInstead = true
61 | )
62 | }
63 |
64 | if (isIGInboxReelAllowed && isViewOpened(
65 | node,
66 | "com.instagram.android:id/reply_bar_container"
67 | )
68 | ) {
69 | return null
70 | }
71 |
72 |
73 | BLOCKED_VIEW_ID_LIST.forEach { viewId ->
74 | if(isViewOpened(node,viewId)){
75 | if (isCooldownActive(viewId)) {
76 | return ViewBlockerResult(isReelFoundInCooldownState = true, viewId = viewId)
77 | }
78 | return ViewBlockerResult(isBlocked = true, viewId = viewId)
79 | }
80 | }
81 | return null
82 | }
83 |
84 | fun applyCooldown(viewId: String, endTime: Long) {
85 | cooldownViewIdsList[viewId] = endTime
86 | }
87 |
88 | private fun isCooldownActive(viewId: String): Boolean {
89 | val cooldownEnd = cooldownViewIdsList[viewId] ?: return false
90 | if (SystemClock.uptimeMillis() > cooldownEnd) {
91 | cooldownViewIdsList.remove(viewId)
92 | return false
93 | }
94 | return true
95 | }
96 |
97 | private fun isViewOpened(rootNode: AccessibilityNodeInfo, viewId: String): Boolean {
98 | val viewNode =
99 | findElementById(rootNode, viewId)
100 | return viewNode != null
101 | }
102 |
103 | private fun isCheatHourActive(): Boolean {
104 |
105 | val currentTime = Calendar.getInstance()
106 | val currentHour = currentTime.get(Calendar.HOUR_OF_DAY)
107 | val currentMinute = currentTime.get(Calendar.MINUTE)
108 |
109 | val currentMinutes = TimeTools.convertToMinutesFromMidnight(currentHour, currentMinute)
110 |
111 | // If cheat hours are not set, treat as inactive
112 | if (cheatMinuteStartTime == null || cheatMinutesEndTIme == null || cheatMinuteStartTime == -1 || cheatMinutesEndTIme == -1) {
113 | return false
114 | }
115 |
116 | return if (cheatMinuteStartTime!! <= cheatMinutesEndTIme!!) {
117 | // Regular case: start time is before or equal to end time
118 | currentMinutes in cheatMinuteStartTime!!..cheatMinutesEndTIme!!
119 | } else {
120 | // Wraparound case: time range spans midnight
121 | currentMinutes in cheatMinuteStartTime!!..1439 || currentMinutes in 0..cheatMinutesEndTIme!!
122 | }
123 | }
124 | data class ViewBlockerResult(
125 | val isBlocked: Boolean = false,
126 | val requestHomePressInstead: Boolean = false,
127 | val isReelFoundInCooldownState: Boolean = false,
128 | val viewId: String = ""
129 | )
130 |
131 | }
132 |
--------------------------------------------------------------------------------
/app/src/main/java/nethical/digipaws/data/blockers/KeywordPacks.kt:
--------------------------------------------------------------------------------
1 | package nethical.digipaws.data.blockers
2 |
3 | class KeywordPacks {
4 | companion object {
5 | val adultKeywords = setOf(
6 | "porn",
7 | "adult",
8 | "xxx",
9 | "erotic",
10 | "nude",
11 | "sex",
12 | "fetish",
13 | "nsfw",
14 | "camgirl",
15 | "explicit",
16 | "hardcore",
17 | "lingerie",
18 | "escort",
19 | "bdsm",
20 | "taboo",
21 | "kink",
22 | "lust",
23 | "voyeur",
24 | "amateur",
25 | "hentai",
26 | "orgy",
27 | "strip",
28 | "milf",
29 | "teens",
30 | "anal",
31 | "lesbian",
32 | "gay",
33 | "blowjob",
34 | "threesome",
35 | "incest",
36 | "submission",
37 | "dominatrix",
38 | "masturbation",
39 | "webcam",
40 | "provocative",
41 | "intercourse",
42 | "hookup",
43 | "seduction",
44 | "scandal",
45 | "temptation",
46 | "nudity",
47 | "orgasm",
48 | "prostitute",
49 | "sultry",
50 | "affair",
51 | "arousal",
52 | "sensual",
53 | "lustful",
54 | "peeping",
55 | "stripper",
56 | "playmate",
57 | "infidelity",
58 | "desire",
59 | "bondage",
60 | "swingers",
61 | "provocateur",
62 | "buceta",
63 | "pussy",
64 | "pelada",
65 | "siririca",
66 | "gozo",
67 | "gozei",
68 | "gozar",
69 | "masturbate"
70 | )
71 | }
72 | }
73 |
--------------------------------------------------------------------------------
/app/src/main/java/nethical/digipaws/receivers/AdminReceiver.kt:
--------------------------------------------------------------------------------
1 | package nethical.digipaws.receivers
2 |
3 | import android.app.admin.DeviceAdminReceiver
4 |
5 | class AdminReceiver : DeviceAdminReceiver()
--------------------------------------------------------------------------------
/app/src/main/java/nethical/digipaws/services/BaseBlockingService.kt:
--------------------------------------------------------------------------------
1 | package nethical.digipaws.services
2 |
3 | import android.accessibilityservice.AccessibilityService
4 | import android.os.SystemClock
5 | import android.view.accessibility.AccessibilityEvent
6 | import nethical.digipaws.utils.SavedPreferencesLoader
7 |
8 | open class BaseBlockingService : AccessibilityService() {
9 | val savedPreferencesLoader: SavedPreferencesLoader by lazy {
10 | SavedPreferencesLoader(this)
11 | }
12 |
13 | var lastBackPressTimeStamp: Long =
14 | SystemClock.uptimeMillis() // prevents repetitive global actions
15 |
16 | override fun onAccessibilityEvent(event: AccessibilityEvent?) {
17 | }
18 |
19 | override fun onInterrupt() {
20 | }
21 |
22 | private fun isDelayOver(): Boolean {
23 | return isDelayOver(1000)
24 | }
25 |
26 | fun isDelayOver(delay: Int): Boolean {
27 | val currentTime = SystemClock.uptimeMillis().toFloat()
28 | return currentTime - lastBackPressTimeStamp > delay
29 | }
30 |
31 | fun isDelayOver(lastTimestamp: Long, delay: Int): Boolean {
32 | val currentTime = SystemClock.uptimeMillis().toFloat()
33 | return currentTime - lastTimestamp > delay
34 | }
35 |
36 | fun pressHome() {
37 | if (isDelayOver()) {
38 | performGlobalAction(GLOBAL_ACTION_HOME)
39 | lastBackPressTimeStamp = SystemClock.uptimeMillis()
40 | }
41 | }
42 |
43 | fun pressBack() {
44 | if (isDelayOver()) {
45 | performGlobalAction(GLOBAL_ACTION_BACK)
46 | lastBackPressTimeStamp = SystemClock.uptimeMillis()
47 | }
48 | }
49 | }
--------------------------------------------------------------------------------
/app/src/main/java/nethical/digipaws/services/KeywordBlockerService.kt:
--------------------------------------------------------------------------------
1 | package nethical.digipaws.services
2 |
3 | import android.annotation.SuppressLint
4 | import android.content.BroadcastReceiver
5 | import android.content.Context
6 | import android.content.Intent
7 | import android.content.IntentFilter
8 | import android.os.Build
9 | import android.os.SystemClock
10 | import android.util.Log
11 | import android.view.accessibility.AccessibilityEvent
12 | import android.view.accessibility.AccessibilityNodeInfo
13 | import android.widget.Toast
14 | import nethical.digipaws.blockers.KeywordBlocker
15 | import nethical.digipaws.data.blockers.KeywordPacks
16 |
17 | class KeywordBlockerService : BaseBlockingService() {
18 |
19 | private var refreshCooldown = 1000
20 | private var lastEventTimeStamp = 0L
21 | companion object {
22 | const val INTENT_ACTION_REFRESH_BLOCKED_KEYWORD_LIST =
23 | "nethical.digipaws.refresh.keywordblocker.blockedwords"
24 |
25 | const val INTENT_ACTION_REFRESH_CONFIG =
26 | "nethical.digipaws.refresh.keywordblocker.config"
27 | }
28 |
29 | private val keywordBlocker = KeywordBlocker(this)
30 |
31 | private var KbIgnoredApps: HashSet = hashSetOf()
32 |
33 | override fun onAccessibilityEvent(event: AccessibilityEvent?) {
34 |
35 | if (!isDelayOver(
36 | lastEventTimeStamp,
37 | refreshCooldown
38 | ) || event == null || event.packageName == "nethical.digipaws" || KbIgnoredApps.contains(
39 | event.packageName
40 | )
41 | ) {
42 | return
43 | }
44 | val rootnode: AccessibilityNodeInfo? = rootInActiveWindow
45 | Log.d("KeywordBlocker", "Searching Keywords")
46 | handleKeywordBlockerResult(keywordBlocker.checkIfUserGettingFreaky(rootnode, event))
47 |
48 | lastEventTimeStamp = SystemClock.uptimeMillis()
49 |
50 | }
51 |
52 | override fun onInterrupt() {
53 | }
54 |
55 | @SuppressLint("UnspecifiedRegisterReceiverFlag")
56 | override fun onServiceConnected() {
57 | super.onServiceConnected()
58 | setupBlockedWords()
59 | setupConfig()
60 |
61 | val filter = IntentFilter().apply {
62 | addAction(INTENT_ACTION_REFRESH_BLOCKED_KEYWORD_LIST)
63 | addAction(INTENT_ACTION_REFRESH_CONFIG)
64 | }
65 | if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.TIRAMISU) {
66 | registerReceiver(refreshReceiver, filter, RECEIVER_EXPORTED)
67 | } else {
68 | registerReceiver(refreshReceiver, filter)
69 | }
70 | }
71 |
72 |
73 | private val refreshReceiver = object : BroadcastReceiver() {
74 | override fun onReceive(context: Context?, intent: Intent?) {
75 | when (intent?.action) {
76 | INTENT_ACTION_REFRESH_BLOCKED_KEYWORD_LIST -> setupBlockedWords()
77 | INTENT_ACTION_REFRESH_CONFIG -> setupConfig()
78 | }
79 | }
80 | }
81 |
82 | private fun handleKeywordBlockerResult(result: KeywordBlocker.KeywordBlockerResult) {
83 | if (result.resultDetectWord == null) return
84 | Toast.makeText(
85 | this,
86 | "Blocked keyword ${result.resultDetectWord} was found.",
87 | Toast.LENGTH_LONG
88 | ).show()
89 | if (result.isHomePressRequested) {
90 | pressHome()
91 | }
92 | }
93 |
94 |
95 | private fun setupBlockedWords() {
96 | val keywords = savedPreferencesLoader.loadBlockedKeywords().toMutableSet()
97 | val sp = getSharedPreferences("keyword_blocker_packs", Context.MODE_PRIVATE)
98 | val isAdultBlockerOn = sp.getBoolean("adult_blocker", false)
99 | if (isAdultBlockerOn) {
100 | keywords.addAll(KeywordPacks.adultKeywords)
101 | }
102 | keywordBlocker.blockedKeyword = keywords.toHashSet()
103 | }
104 |
105 | private fun setupConfig() {
106 | val sp = getSharedPreferences("keyword_blocker_configs", Context.MODE_PRIVATE)
107 |
108 | keywordBlocker.isSearchAllTextFields = sp.getBoolean("search_all_text_fields", false)
109 | keywordBlocker.redirectUrl =
110 | sp.getString("redirect_url", "https://www.youtube.com/watch?v=x31tDT-4fQw&t=1s")
111 | .toString()
112 |
113 | if (keywordBlocker.isSearchAllTextFields) {
114 | refreshCooldown = 5000
115 | }
116 |
117 | KbIgnoredApps = savedPreferencesLoader.getKeywordBlockerIgnoredApps().toHashSet()
118 |
119 | }
120 |
121 |
122 |
123 | override fun onDestroy() {
124 | super.onDestroy()
125 | unregisterReceiver(refreshReceiver)
126 | }
127 | }
--------------------------------------------------------------------------------
/app/src/main/java/nethical/digipaws/ui/activity/FragmentActivity.kt:
--------------------------------------------------------------------------------
1 | package nethical.digipaws.ui.activity
2 |
3 | import android.os.Bundle
4 | import androidx.activity.enableEdgeToEdge
5 | import androidx.appcompat.app.AppCompatActivity
6 | import androidx.core.view.ViewCompat
7 | import androidx.core.view.WindowInsetsCompat
8 | import androidx.fragment.app.Fragment
9 | import nethical.digipaws.R
10 | import nethical.digipaws.ui.fragments.anti_uninstall.ChooseModeFragment
11 | import nethical.digipaws.ui.fragments.installation.AccessibilityGuide
12 | import nethical.digipaws.ui.fragments.installation.WelcomeFragment
13 | import nethical.digipaws.ui.fragments.usage.AllAppsUsageFragment
14 |
15 | class FragmentActivity : AppCompatActivity() {
16 |
17 | override fun onCreate(savedInstanceState: Bundle?) {
18 | super.onCreate(savedInstanceState)
19 | enableEdgeToEdge()
20 | setContentView(R.layout.activity_fragment)
21 | ViewCompat.setOnApplyWindowInsetsListener(findViewById(R.id.main)) { v, insets ->
22 | val systemBars = insets.getInsets(WindowInsetsCompat.Type.systemBars())
23 | v.setPadding(systemBars.left, systemBars.top, systemBars.right, systemBars.bottom)
24 | insets
25 | }
26 |
27 | var fragment: Fragment? = null
28 | if (intent.getStringExtra("fragment") != null) {
29 | when (intent.getStringExtra("fragment")) {
30 | ChooseModeFragment.FRAGMENT_ID -> {
31 | fragment = ChooseModeFragment()
32 | }
33 | AllAppsUsageFragment.FRAGMENT_ID -> {
34 | fragment = AllAppsUsageFragment()
35 | }
36 | WelcomeFragment.FRAGMENT_ID -> {
37 | fragment = WelcomeFragment()
38 | }
39 | AccessibilityGuide.FRAGMENT_ID ->
40 | fragment = AccessibilityGuide()
41 | }
42 | supportFragmentManager.beginTransaction()
43 | .replace(
44 | R.id.fragment_holder,
45 | fragment!!
46 | ) // Add or replace the fragment in the container
47 | .commit() // Commit the transaction
48 | }
49 | }
50 | }
--------------------------------------------------------------------------------
/app/src/main/java/nethical/digipaws/ui/activity/ShortcutActivity.kt:
--------------------------------------------------------------------------------
1 | package nethical.digipaws.ui.activity
2 |
3 | import android.accessibilityservice.AccessibilityService
4 | import android.content.ComponentName
5 | import android.content.Intent
6 | import android.os.Bundle
7 | import android.provider.Settings
8 | import android.widget.Toast
9 | import androidx.appcompat.app.AppCompatActivity
10 | import nethical.digipaws.services.GeneralFeaturesService
11 | import nethical.digipaws.ui.dialogs.StartFocusMode
12 | import nethical.digipaws.utils.SavedPreferencesLoader
13 |
14 | class ShortcutActivity : AppCompatActivity() {
15 |
16 | private val savedPreferencesLoader = SavedPreferencesLoader(this)
17 |
18 | override fun onCreate(savedInstanceState: Bundle?) {
19 | super.onCreate(savedInstanceState)
20 |
21 | val isFocusedModeOn = savedPreferencesLoader.getFocusModeData().isTurnedOn
22 | if(isFocusedModeOn){
23 | Toast.makeText(this,"Focus Mode is already active",Toast.LENGTH_SHORT).show()
24 | finish()
25 | }
26 |
27 | val isGeneralSettingsOn = isAccessibilityServiceEnabled(GeneralFeaturesService::class.java)
28 | if(!isGeneralSettingsOn){
29 | Toast.makeText(this,"Find 'General Features' and press enable",Toast.LENGTH_LONG).show()
30 | openAccessibilityServiceScreen(cls = GeneralFeaturesService::class.java)
31 | finish()
32 | }
33 | StartFocusMode(savedPreferencesLoader, onPositiveButtonPressed = {
34 | finish()
35 | }).show(
36 | supportFragmentManager,
37 | "start_focus_mode_from_shortcut"
38 | )
39 | }
40 |
41 |
42 | private fun openAccessibilityServiceScreen(cls: Class<*>) {
43 | try {
44 | val intent = Intent(Settings.ACTION_ACCESSIBILITY_SETTINGS)
45 | val componentName = ComponentName(this, cls)
46 |
47 | intent.putExtra(":settings:fragment_args_key", componentName.flattenToString())
48 | startActivity(intent)
49 | } catch (e: Exception) {
50 | e.printStackTrace()
51 | // Fallback to general Accessibility Settings
52 | startActivity(Intent(Settings.ACTION_ACCESSIBILITY_SETTINGS))
53 | }
54 | }
55 |
56 | private fun isAccessibilityServiceEnabled(serviceClass: Class): Boolean {
57 | val serviceName = ComponentName(this, serviceClass).flattenToString()
58 | val enabledServices = Settings.Secure.getString(
59 | contentResolver,
60 | Settings.Secure.ENABLED_ACCESSIBILITY_SERVICES
61 | ) ?: return false
62 | val isAccessibilityEnabled = Settings.Secure.getInt(
63 | contentResolver,
64 | Settings.Secure.ACCESSIBILITY_ENABLED,
65 | 0
66 | )
67 | return isAccessibilityEnabled == 1 && enabledServices.contains(serviceName)
68 | }
69 | }
--------------------------------------------------------------------------------
/app/src/main/java/nethical/digipaws/ui/dialogs/BaseDialog.kt:
--------------------------------------------------------------------------------
1 | package nethical.digipaws.ui.dialogs
2 |
3 | import android.annotation.SuppressLint
4 | import android.content.Intent
5 | import android.view.MotionEvent
6 | import android.widget.ScrollView
7 | import androidx.fragment.app.DialogFragment
8 | import nethical.digipaws.utils.SavedPreferencesLoader
9 | import nl.joery.timerangepicker.TimeRangePicker
10 |
11 | open class BaseDialog(val savedPreferencesLoader: SavedPreferencesLoader? = null) :
12 | DialogFragment() {
13 | fun sendRefreshRequest(action: String) {
14 | val intent = Intent(action)
15 | context?.sendBroadcast(intent)
16 | }
17 |
18 |
19 | @SuppressLint("ClickableViewAccessibility")
20 | fun fixPickerInterceptBug(scrollview: ScrollView, picker: TimeRangePicker) {
21 | picker.setOnTouchListener { v, event ->
22 | // Disable ScrollView's touch interception when interacting with the picker
23 | when (event.action) {
24 | MotionEvent.ACTION_DOWN -> scrollview.requestDisallowInterceptTouchEvent(true)
25 | MotionEvent.ACTION_UP, MotionEvent.ACTION_CANCEL -> scrollview.requestDisallowInterceptTouchEvent(
26 | false
27 | )
28 | }
29 | v.onTouchEvent(event) // Pass the event to the picker
30 | }
31 | }
32 | }
--------------------------------------------------------------------------------
/app/src/main/java/nethical/digipaws/ui/dialogs/StartFocusMode.kt:
--------------------------------------------------------------------------------
1 | package nethical.digipaws.ui.dialogs
2 |
3 | import android.app.Dialog
4 | import android.os.Bundle
5 | import com.google.android.material.dialog.MaterialAlertDialogBuilder
6 | import nethical.digipaws.Constants
7 | import nethical.digipaws.R
8 | import nethical.digipaws.blockers.FocusModeBlocker
9 | import nethical.digipaws.databinding.DialogFocusModeBinding
10 | import nethical.digipaws.services.AppBlockerService
11 | import nethical.digipaws.utils.NotificationTimerManager
12 | import nethical.digipaws.utils.SavedPreferencesLoader
13 |
14 | class StartFocusMode(savedPreferencesLoader: SavedPreferencesLoader,private val onPositiveButtonPressed: () -> Unit) : BaseDialog(
15 | savedPreferencesLoader
16 | ) {
17 |
18 | override fun onCreateDialog(savedInstanceState: Bundle?): Dialog {
19 | // Inflate the custom dialog layout
20 | val dialogFocusModeBinding = DialogFocusModeBinding.inflate(layoutInflater)
21 | val previousData = savedPreferencesLoader?.getFocusModeData()
22 | dialogFocusModeBinding.focusModeMinsPicker.setValue(3)
23 | dialogFocusModeBinding.focusModeMinsPicker.minValue = 2
24 |
25 | var selectedMode = previousData?.modeType
26 | if (previousData != null) {
27 | when (previousData.modeType) {
28 | Constants.FOCUS_MODE_BLOCK_SELECTED -> dialogFocusModeBinding.blockSelected.isChecked =
29 | true
30 |
31 | Constants.FOCUS_MODE_BLOCK_ALL_EX_SELECTED -> dialogFocusModeBinding.blockAll.isChecked =
32 | true
33 | }
34 | }
35 |
36 | dialogFocusModeBinding.modeType.setOnCheckedChangeListener { _, checkedId ->
37 | when (checkedId) {
38 | dialogFocusModeBinding.blockAll.id -> selectedMode =
39 | Constants.FOCUS_MODE_BLOCK_ALL_EX_SELECTED
40 |
41 | dialogFocusModeBinding.blockSelected.id -> selectedMode =
42 | Constants.FOCUS_MODE_BLOCK_SELECTED
43 | }
44 | }
45 | return MaterialAlertDialogBuilder(requireContext())
46 | .setView(dialogFocusModeBinding.root)
47 | .setPositiveButton(getString(R.string.start)) { _, _ ->
48 | val totalMillis = dialogFocusModeBinding.focusModeMinsPicker.getValue() * 60000
49 | savedPreferencesLoader?.saveFocusModeData(
50 | FocusModeBlocker.FocusModeData(
51 | true,
52 | System.currentTimeMillis() + totalMillis,
53 | selectedMode!!
54 | )
55 | )
56 | sendRefreshRequest(AppBlockerService.INTENT_ACTION_REFRESH_FOCUS_MODE)
57 | val timer = NotificationTimerManager(requireContext())
58 | // TODO: add notification permission check
59 | timer.startTimer(totalMillis.toLong())
60 | onPositiveButtonPressed()
61 | }
62 | .setNegativeButton(getString(R.string.cancel), null)
63 | .show()
64 | }
65 |
66 | }
67 |
--------------------------------------------------------------------------------
/app/src/main/java/nethical/digipaws/ui/dialogs/TweakAppBlockerWarning.kt:
--------------------------------------------------------------------------------
1 | package nethical.digipaws.ui.dialogs
2 |
3 | import android.animation.LayoutTransition
4 | import android.app.Dialog
5 | import android.os.Bundle
6 | import android.util.Log
7 | import com.google.android.material.chip.Chip
8 | import com.google.android.material.dialog.MaterialAlertDialogBuilder
9 | import nethical.digipaws.R
10 | import nethical.digipaws.databinding.DialogTweakBlockerWarningBinding
11 | import nethical.digipaws.services.AppBlockerService
12 | import nethical.digipaws.ui.activity.MainActivity
13 | import nethical.digipaws.utils.AnimTools.Companion.animateVisibility
14 | import nethical.digipaws.utils.SavedPreferencesLoader
15 |
16 | class TweakAppBlockerWarning(savedPreferencesLoader: SavedPreferencesLoader) : BaseDialog(
17 | savedPreferencesLoader
18 | ) {
19 |
20 | override fun onCreateDialog(savedInstanceState: Bundle?): Dialog {
21 | // Inflate the custom dialog layout
22 | val binding = DialogTweakBlockerWarningBinding.inflate(layoutInflater)
23 |
24 | // Set up number picker
25 | binding.selectMins.minValue = 1
26 | binding.selectMins.maxValue = 240
27 |
28 | binding.cbProceedBtn.setOnCheckedChangeListener { _, isChecked ->
29 | val viewsToToggle = listOf(
30 | binding.cbDynamicWarning,
31 | binding.selectMins,
32 | binding.info,
33 | binding.proceedDelay
34 | )
35 | viewsToToggle.forEach { it.animateVisibility(!isChecked) }
36 |
37 | }
38 |
39 | binding.cbBackWithoutWarning.setOnCheckedChangeListener { _, isChecked ->
40 | val viewsToToggle = listOf(
41 | binding.cbDynamicWarning,
42 | binding.selectMins,
43 | binding.textInputLayout2,
44 | binding.info,
45 | binding.cbProceedBtn,
46 | binding.proceedDelay
47 | )
48 | viewsToToggle.forEach { it.animateVisibility(!isChecked) }
49 | }
50 |
51 | // Load previous data from preferences
52 | val previousData = savedPreferencesLoader!!.loadAppBlockerWarningInfo()
53 | var proceedDelay = previousData.proceedDelayInSecs
54 |
55 | when (proceedDelay) {
56 | 3 -> binding.proceedDelayChips.check(R.id.three_sec_chip)
57 | 9 -> binding.proceedDelayChips.check(R.id.nine_sec_chip)
58 | 30 -> binding.proceedDelayChips.check(R.id.thirty_sec_chip)
59 | 15 -> binding.proceedDelayChips.check(R.id.fifteen_sec_chip)
60 | }
61 |
62 | binding.proceedDelayChips.setOnCheckedStateChangeListener { group, checkedIds ->
63 |
64 | val chip = group.findViewById(checkedIds[0])
65 | proceedDelay = chip.text.toString().slice(IntRange(0, 1)).trim().toInt()
66 | Log.d("proceedDelay", "onCreateDialog: $proceedDelay")
67 | }
68 |
69 | previousData.let {
70 | binding.selectMins.setValue(it.timeInterval / 60000)
71 | binding.warningMsgEdit.setText(it.message)
72 | binding.cbProceedBtn.isChecked = it.isProceedDisabled
73 | binding.cbBackWithoutWarning.isChecked = it.isWarningDialogHidden
74 | }
75 |
76 | binding.root.layoutTransition = LayoutTransition().apply {
77 | enableTransitionType(LayoutTransition.CHANGING)
78 | setDuration(300) // Set animation duration in ms
79 | }
80 |
81 | // Build and return the dialog
82 | return MaterialAlertDialogBuilder(requireContext())
83 | .setView(binding.root)
84 | .setPositiveButton(getString(R.string.save)) { dialog, _ ->
85 | val selectedMinInMs = binding.selectMins.getValue() * 60000
86 | savedPreferencesLoader.saveAppBlockerWarningInfo(
87 | MainActivity.WarningData(
88 | binding.warningMsgEdit.text.toString(),
89 | selectedMinInMs,
90 | binding.cbDynamicWarning.isChecked,
91 | binding.cbProceedBtn.isChecked,
92 | binding.cbBackWithoutWarning.isChecked,
93 | proceedDelay
94 | )
95 | )
96 | sendRefreshRequest(AppBlockerService.INTENT_ACTION_REFRESH_APP_BLOCKER)
97 | dialog.dismiss()
98 | }
99 | .setCancelable(false)
100 | .setNegativeButton(getString(R.string.cancel)) { dialog, _ ->
101 | dialog.dismiss()
102 | }
103 | .create()
104 |
105 | }
106 |
107 | }
108 |
--------------------------------------------------------------------------------
/app/src/main/java/nethical/digipaws/ui/dialogs/TweakGrayScaleMode.kt:
--------------------------------------------------------------------------------
1 | package nethical.digipaws.ui.dialogs
2 |
3 | import android.annotation.SuppressLint
4 | import android.app.Dialog
5 | import android.content.Context
6 | import android.content.SharedPreferences
7 | import android.os.Bundle
8 | import com.google.android.material.dialog.MaterialAlertDialogBuilder
9 | import nethical.digipaws.Constants
10 | import nethical.digipaws.R
11 | import nethical.digipaws.databinding.DialogGrayscaleBinding
12 | import nethical.digipaws.services.GeneralFeaturesService
13 | import nethical.digipaws.utils.GrayscaleControl
14 | import nethical.digipaws.utils.SavedPreferencesLoader
15 |
16 | class TweakGrayScaleMode(
17 | savedPreferencesLoader: SavedPreferencesLoader
18 | ) : BaseDialog(savedPreferencesLoader) {
19 |
20 | private lateinit var trackerPreferences: SharedPreferences
21 |
22 | @SuppressLint("ApplySharedPref")
23 | override fun onCreateDialog(savedInstanceState: Bundle?): Dialog {
24 | val dialogGrayscaleBinding = DialogGrayscaleBinding.inflate(layoutInflater)
25 |
26 | // Load tracker preferences
27 | trackerPreferences =
28 | requireContext().getSharedPreferences("grayscale", Context.MODE_PRIVATE)
29 | val getMode = trackerPreferences.getInt("mode",Constants.GRAYSCALE_MODE_ONLY_SELECTED)
30 |
31 |
32 |
33 | when(getMode){
34 | Constants.GRAYSCALE_MODE_ONLY_SELECTED -> {
35 | dialogGrayscaleBinding.blockSelected.isChecked = true
36 | }
37 | Constants.GRAYSCALE_MODE_ALL -> {
38 | dialogGrayscaleBinding.blockAll.isChecked = true
39 | }
40 | Constants.GRAYSCALE_MODE_ALL_EXCEPT_SELECTED -> {
41 | dialogGrayscaleBinding.blockExceptSelected.isChecked = true
42 | }
43 | }
44 |
45 | // Build and display dialog
46 | return MaterialAlertDialogBuilder(requireContext())
47 | .setView(dialogGrayscaleBinding.root)
48 | .setCancelable(false)
49 | .setPositiveButton(getString(R.string.save)) { dialog, _ ->
50 | when(dialogGrayscaleBinding.modeType.checkedRadioButtonId){
51 | dialogGrayscaleBinding.blockAll.id -> {
52 | trackerPreferences.edit().putInt("mode",Constants.GRAYSCALE_MODE_ALL).commit()
53 | val grayscaleControl = GrayscaleControl()
54 | grayscaleControl.enableGrayscale()
55 | }
56 | dialogGrayscaleBinding.turnOff.id -> {
57 | trackerPreferences.edit().putInt("mode",Constants.GRAYSCALE_MODE_OFF).commit()
58 | val grayscaleControl = GrayscaleControl()
59 | grayscaleControl.disableGrayscale()
60 | }
61 | dialogGrayscaleBinding.blockSelected.id -> {
62 | trackerPreferences.edit().putInt("mode",Constants.GRAYSCALE_MODE_ONLY_SELECTED).commit()
63 | }
64 | dialogGrayscaleBinding.blockExceptSelected.id -> {
65 | trackerPreferences.edit().putInt("mode",Constants.GRAYSCALE_MODE_ALL_EXCEPT_SELECTED).commit()
66 | }
67 |
68 | }
69 | // Send broadcast to refresh UsageTrackingService
70 | sendRefreshRequest(GeneralFeaturesService.INTENT_ACTION_REFRESH_GRAYSCALE)
71 | dialog.dismiss()
72 | }
73 | .setNegativeButton(getString(R.string.cancel)) { dialog, _ ->
74 | dialog.dismiss()
75 | }
76 | .create()
77 | }
78 | }
79 |
--------------------------------------------------------------------------------
/app/src/main/java/nethical/digipaws/ui/dialogs/TweakKeywordBlocker.kt:
--------------------------------------------------------------------------------
1 | package nethical.digipaws.ui.dialogs
2 |
3 | import android.annotation.SuppressLint
4 | import android.app.Dialog
5 | import android.content.Context
6 | import android.content.Intent
7 | import android.content.SharedPreferences
8 | import android.os.Bundle
9 | import android.view.View
10 | import androidx.activity.result.ActivityResultLauncher
11 | import androidx.activity.result.contract.ActivityResultContracts
12 | import androidx.appcompat.app.AppCompatActivity.RESULT_OK
13 | import androidx.core.app.ActivityOptionsCompat
14 | import com.google.android.material.dialog.MaterialAlertDialogBuilder
15 | import nethical.digipaws.R
16 | import nethical.digipaws.databinding.DialogKeywordBlockerConfigBinding
17 | import nethical.digipaws.services.KeywordBlockerService
18 | import nethical.digipaws.ui.activity.SelectAppsActivity
19 | import nethical.digipaws.utils.SavedPreferencesLoader
20 |
21 | class TweakKeywordBlocker(savedPreferencesLoader: SavedPreferencesLoader) :
22 | BaseDialog(savedPreferencesLoader) {
23 |
24 | private lateinit var sharedPreferences: SharedPreferences
25 |
26 | private var ignoredAppsSize = 0
27 |
28 | @SuppressLint("ApplySharedPref", "SetTextI18n")
29 | override fun onCreateDialog(savedInstanceState: Bundle?): Dialog {
30 | val dialogManageKeywordBlocker = DialogKeywordBlockerConfigBinding.inflate(layoutInflater)
31 |
32 |
33 | val selectIgnoredApps: ActivityResultLauncher =
34 | registerForActivityResult(ActivityResultContracts.StartActivityForResult()) { result ->
35 | if (result.resultCode == RESULT_OK) {
36 | val selectedApps = result.data?.getStringArrayListExtra("SELECTED_APPS")
37 | selectedApps?.let {
38 | ignoredAppsSize = selectedApps.size
39 | dialogManageKeywordBlocker.ignoredKbApps.setText(dialogManageKeywordBlocker.ignoredKbApps.text.toString() + " " + "($ignoredAppsSize)")
40 | savedPreferencesLoader?.saveKeywordBlockerIgnoredApps(selectedApps)
41 | }
42 | }
43 | }
44 | if (!dialogManageKeywordBlocker.cbSearchTextField.isChecked) {
45 | dialogManageKeywordBlocker.ignoredKbApps.visibility = View.GONE
46 | }
47 | dialogManageKeywordBlocker.cbSearchTextField.setOnCheckedChangeListener { _, isChecked ->
48 | if (isChecked) {
49 | dialogManageKeywordBlocker.ignoredKbApps.visibility = View.VISIBLE
50 | } else {
51 | dialogManageKeywordBlocker.ignoredKbApps.visibility = View.GONE
52 | }
53 | }
54 |
55 | dialogManageKeywordBlocker.ignoredKbApps.setOnClickListener {
56 |
57 | val intent = Intent(requireContext(), SelectAppsActivity::class.java)
58 | intent.putStringArrayListExtra(
59 | "PRE_SELECTED_APPS",
60 | ArrayList(savedPreferencesLoader?.getKeywordBlockerIgnoredApps() ?: emptyList())
61 | )
62 | selectIgnoredApps.launch(
63 | intent,
64 | ActivityOptionsCompat.makeCustomAnimation(
65 | requireContext(),
66 | R.anim.fade_in,
67 | R.anim.fade_out
68 | )
69 | )
70 | }
71 |
72 | // Initialize SharedPreferences
73 | sharedPreferences =
74 | requireContext().getSharedPreferences("keyword_blocker_configs", Context.MODE_PRIVATE)
75 |
76 | // Load current preferences into dialog
77 | dialogManageKeywordBlocker.cbSearchTextField.isChecked =
78 | sharedPreferences.getBoolean("search_all_text_fields", false)
79 | dialogManageKeywordBlocker.redirectUrl.setText(
80 | sharedPreferences.getString(
81 | "redirect_url",
82 | "https://www.youtube.com/watch?v=x31tDT-4fQw&t=1s"
83 | )
84 | )
85 |
86 | // Build and show the dialog
87 | return MaterialAlertDialogBuilder(requireContext())
88 | .setView(dialogManageKeywordBlocker.root)
89 | .setCancelable(false)
90 | .setPositiveButton(getString(R.string.ok)) { _, _ ->
91 | // Save changes to SharedPreferences
92 | with(sharedPreferences.edit()) {
93 | putBoolean(
94 | "search_all_text_fields",
95 | dialogManageKeywordBlocker.cbSearchTextField.isChecked
96 | )
97 | putString(
98 | "redirect_url",
99 | dialogManageKeywordBlocker.redirectUrl.text.toString()
100 | )
101 | commit() // Save changes immediately
102 | }
103 |
104 | // Send broadcast to refresh the KeywordBlockerService
105 | sendRefreshRequest(KeywordBlockerService.INTENT_ACTION_REFRESH_CONFIG)
106 | }
107 | .setNegativeButton(getString(R.string.close)) { _, _ ->
108 | // Do nothing on cancel
109 | }
110 | .create()
111 | }
112 |
113 | }
114 |
--------------------------------------------------------------------------------
/app/src/main/java/nethical/digipaws/ui/dialogs/TweakKeywordPack.kt:
--------------------------------------------------------------------------------
1 | package nethical.digipaws.ui.dialogs
2 |
3 | import android.annotation.SuppressLint
4 | import android.app.Dialog
5 | import android.content.Context
6 | import android.content.SharedPreferences
7 | import android.os.Bundle
8 | import com.google.android.material.dialog.MaterialAlertDialogBuilder
9 | import nethical.digipaws.R
10 | import nethical.digipaws.databinding.DialogKeywordPackageBinding
11 | import nethical.digipaws.services.KeywordBlockerService
12 |
13 | class TweakKeywordPack : BaseDialog() {
14 |
15 | private lateinit var sharedPreferences: SharedPreferences
16 |
17 | @SuppressLint("ApplySharedPref")
18 | override fun onCreateDialog(savedInstanceState: Bundle?): Dialog {
19 | val dialogManageKeywordPacks = DialogKeywordPackageBinding.inflate(layoutInflater)
20 |
21 | // Initialize SharedPreferences
22 | sharedPreferences =
23 | requireContext().getSharedPreferences("keyword_blocker_packs", Context.MODE_PRIVATE)
24 |
25 | // Load current preferences into dialog
26 | dialogManageKeywordPacks.cbAdultKeywords.isChecked =
27 | sharedPreferences.getBoolean("adult_blocker", false)
28 |
29 | // Build and show the dialog
30 | return MaterialAlertDialogBuilder(requireContext())
31 | .setTitle(getString(R.string.manage_keyword_blockers))
32 | .setView(dialogManageKeywordPacks.root)
33 | .setCancelable(false)
34 | .setPositiveButton(getString(R.string.save)) { _, _ ->
35 | // Save changes to SharedPreferences
36 | with(sharedPreferences.edit()) {
37 | putBoolean(
38 | "adult_blocker",
39 | dialogManageKeywordPacks.cbAdultKeywords.isChecked
40 | )
41 | commit() // Save changes immediately
42 | }
43 |
44 | // Send broadcast to refresh the KeywordBlockerService
45 | sendRefreshRequest(KeywordBlockerService.INTENT_ACTION_REFRESH_BLOCKED_KEYWORD_LIST)
46 | }
47 | .setNegativeButton(getString(R.string.cancel)) { dialog, _ ->
48 | dialog.dismiss()
49 | }
50 | .create()
51 | }
52 |
53 | }
54 |
--------------------------------------------------------------------------------
/app/src/main/java/nethical/digipaws/ui/dialogs/TweakUsageTracker.kt:
--------------------------------------------------------------------------------
1 | package nethical.digipaws.ui.dialogs
2 |
3 | import android.annotation.SuppressLint
4 | import android.app.Dialog
5 | import android.content.Context
6 | import android.content.SharedPreferences
7 | import android.os.Bundle
8 | import com.google.android.material.dialog.MaterialAlertDialogBuilder
9 | import nethical.digipaws.R
10 | import nethical.digipaws.databinding.DialogConfigTrackerBinding
11 | import nethical.digipaws.services.UsageTrackingService
12 | import nethical.digipaws.utils.SavedPreferencesLoader
13 |
14 | class TweakUsageTracker(
15 | savedPreferencesLoader: SavedPreferencesLoader
16 | ) : BaseDialog(savedPreferencesLoader) {
17 |
18 | private lateinit var trackerPreferences: SharedPreferences
19 |
20 | @SuppressLint("ApplySharedPref")
21 | override fun onCreateDialog(savedInstanceState: Bundle?): Dialog {
22 | val dialogConfigurationTracker = DialogConfigTrackerBinding.inflate(layoutInflater)
23 |
24 | // Load tracker preferences
25 | trackerPreferences =
26 | requireContext().getSharedPreferences("config_tracker", Context.MODE_PRIVATE)
27 | dialogConfigurationTracker.cbReelCounter.isChecked =
28 | trackerPreferences.getBoolean("is_reel_counter", true)
29 | dialogConfigurationTracker.cbTimeElapsed.isChecked =
30 | trackerPreferences.getBoolean("is_time_elapsed", false)
31 |
32 | // Build and display dialog
33 | return MaterialAlertDialogBuilder(requireContext())
34 | .setView(dialogConfigurationTracker.root)
35 | .setCancelable(false)
36 | .setPositiveButton(getString(R.string.save)) { dialog, _ ->
37 |
38 | // Save updated settings
39 | with(trackerPreferences.edit()) {
40 | putBoolean(
41 | "is_reel_counter",
42 | dialogConfigurationTracker.cbReelCounter.isChecked
43 | )
44 | putBoolean(
45 | "is_time_elapsed",
46 | dialogConfigurationTracker.cbTimeElapsed.isChecked
47 | )
48 | commit() // Apply changes immediately
49 | }
50 |
51 | // Send broadcast to refresh UsageTrackingService
52 | sendRefreshRequest(UsageTrackingService.INTENT_ACTION_REFRESH_USAGE_TRACKER)
53 | dialog.dismiss()
54 | }
55 | .setNegativeButton(getString(R.string.cancel)) { dialog, _ ->
56 | dialog.dismiss()
57 | }
58 | .create()
59 | }
60 | }
61 |
--------------------------------------------------------------------------------
/app/src/main/java/nethical/digipaws/ui/dialogs/TweakViewBlockerCheatHours.kt:
--------------------------------------------------------------------------------
1 | package nethical.digipaws.ui.dialogs
2 |
3 | import android.app.Dialog
4 | import android.content.Context
5 | import android.os.Bundle
6 | import android.view.View
7 | import android.widget.Toast
8 | import com.google.android.material.dialog.MaterialAlertDialogBuilder
9 | import nethical.digipaws.R
10 | import nethical.digipaws.databinding.DialogAddTimedActionBinding
11 | import nethical.digipaws.services.ViewBlockerService
12 | import nethical.digipaws.utils.SavedPreferencesLoader
13 | import nl.joery.timerangepicker.TimeRangePicker
14 |
15 | class TweakViewBlockerCheatHours(savedPreferencesLoader: SavedPreferencesLoader) : BaseDialog(
16 | savedPreferencesLoader
17 | ) {
18 |
19 | private var startTimeInMins: Int? = null
20 | private var endTimeInMins: Int? = null
21 |
22 | override fun onCreateDialog(savedInstanceState: Bundle?): Dialog {
23 |
24 | val dialogAddToCheatHoursBinding = DialogAddTimedActionBinding.inflate(layoutInflater)
25 |
26 | // Hide unused UI elements
27 | dialogAddToCheatHoursBinding.btnSelectUnblockedApps.visibility = View.GONE
28 | dialogAddToCheatHoursBinding.cheatHourTitle.visibility = View.GONE
29 |
30 | // Configure time picker
31 | dialogAddToCheatHoursBinding.picker.hourFormat = TimeRangePicker.HourFormat.FORMAT_24
32 | fixPickerInterceptBug(
33 | dialogAddToCheatHoursBinding.scrollview,
34 | dialogAddToCheatHoursBinding.picker
35 | )
36 |
37 | val viewBlockerCheatHours =
38 | requireContext().getSharedPreferences("cheat_hours", Context.MODE_PRIVATE)
39 | val savedEndTimeInMinutes = viewBlockerCheatHours.getInt("view_blocker_end_time", -1)
40 | val savedStartTimeInMinutes = viewBlockerCheatHours.getInt("view_blocker_start_time", -1)
41 |
42 | // Set saved times if available
43 | if (savedStartTimeInMinutes != -1 || savedEndTimeInMinutes != -1) {
44 | dialogAddToCheatHoursBinding.picker.startTimeMinutes = savedStartTimeInMinutes
45 | dialogAddToCheatHoursBinding.picker.endTimeMinutes = savedEndTimeInMinutes
46 | startTimeInMins = savedStartTimeInMinutes
47 | endTimeInMins = savedEndTimeInMinutes
48 | } else {
49 | dialogAddToCheatHoursBinding.picker.startTimeMinutes = 0
50 | dialogAddToCheatHoursBinding.picker.endTimeMinutes = 0
51 | dialogAddToCheatHoursBinding.fromTime.text = getString(R.string.from)
52 | dialogAddToCheatHoursBinding.endTime.text = getString(R.string.end)
53 | }
54 |
55 | // Handle time changes
56 | dialogAddToCheatHoursBinding.picker.setOnTimeChangeListener(object :
57 | TimeRangePicker.OnTimeChangeListener {
58 | override fun onStartTimeChange(startTime: TimeRangePicker.Time) {
59 | dialogAddToCheatHoursBinding.fromTime.text =
60 | dialogAddToCheatHoursBinding.picker.startTime.toString()
61 | startTimeInMins = dialogAddToCheatHoursBinding.picker.startTimeMinutes
62 | endTimeInMins = dialogAddToCheatHoursBinding.picker.endTimeMinutes
63 | }
64 |
65 | override fun onEndTimeChange(endTime: TimeRangePicker.Time) {
66 | dialogAddToCheatHoursBinding.endTime.text =
67 | dialogAddToCheatHoursBinding.picker.endTime.toString()
68 | startTimeInMins = dialogAddToCheatHoursBinding.picker.startTimeMinutes
69 | endTimeInMins = dialogAddToCheatHoursBinding.picker.endTimeMinutes
70 | }
71 |
72 | override fun onDurationChange(duration: TimeRangePicker.TimeDuration) {
73 | // No action needed
74 | }
75 | })
76 |
77 | // Show dialog
78 | return MaterialAlertDialogBuilder(requireContext())
79 | .setView(dialogAddToCheatHoursBinding.root)
80 | .setCancelable(false)
81 | .setPositiveButton(getString(R.string.save)) { dialog, _ ->
82 | if (startTimeInMins == null || endTimeInMins == null) {
83 | Toast.makeText(
84 | requireContext(),
85 | getString(R.string.please_specify_time),
86 | Toast.LENGTH_SHORT
87 | ).show()
88 | return@setPositiveButton
89 | }
90 | savedPreferencesLoader!!.saveCheatHoursForViewBlocker(
91 | startTimeInMins!!,
92 | endTimeInMins!!
93 | )
94 | sendRefreshRequest(ViewBlockerService.INTENT_ACTION_REFRESH_VIEW_BLOCKER)
95 | dialog.dismiss()
96 | }
97 | .setNegativeButton(getString(R.string.cancel)) { dialog, _ ->
98 | dialog.dismiss()
99 | }
100 | .create()
101 | }
102 | }
103 |
104 |
--------------------------------------------------------------------------------
/app/src/main/java/nethical/digipaws/ui/fragments/anti_uninstall/ChooseModeFragment.kt:
--------------------------------------------------------------------------------
1 | package nethical.digipaws.ui.fragments.anti_uninstall
2 |
3 | import android.os.Bundle
4 | import android.view.LayoutInflater
5 | import android.view.View
6 | import android.view.ViewGroup
7 | import androidx.fragment.app.Fragment
8 | import nethical.digipaws.R
9 | import nethical.digipaws.databinding.FragmentChoseAntiUninstallModeBinding
10 |
11 | class ChooseModeFragment : Fragment() {
12 |
13 | companion object {
14 | const val FRAGMENT_ID = "choose_anti_uninstall_mode"
15 | }
16 | private var _binding: FragmentChoseAntiUninstallModeBinding? = null
17 | private val binding get() = _binding!! // Safe getter for binding
18 |
19 | override fun onCreateView(
20 | inflater: LayoutInflater,
21 | container: ViewGroup?,
22 | savedInstanceState: Bundle?
23 | ): View {
24 | _binding = FragmentChoseAntiUninstallModeBinding.inflate(inflater, container, false)
25 | return binding.root
26 | }
27 |
28 | override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
29 | super.onViewCreated(view, savedInstanceState)
30 | binding.btnNext.setOnClickListener {
31 | when (binding.radioGroup.checkedRadioButtonId) {
32 | binding.passMode.id -> {
33 | requireActivity().supportFragmentManager.beginTransaction()
34 | .replace(
35 | R.id.fragment_holder,
36 | SetupPasswordModeFragment()
37 | ) // Replace with FragmentB
38 | .addToBackStack(null)
39 | .commit()
40 | }
41 |
42 | binding.timedMode.id -> {
43 | requireActivity().supportFragmentManager.beginTransaction()
44 | .replace(
45 | R.id.fragment_holder,
46 | SetupTimedModeFragment()
47 | ) // Replace with FragmentB
48 | .addToBackStack(null)
49 | .commit()
50 | }
51 | }
52 | }
53 | }
54 |
55 | override fun onDestroyView() {
56 | super.onDestroyView()
57 | _binding = null
58 | }
59 | }
60 |
--------------------------------------------------------------------------------
/app/src/main/java/nethical/digipaws/ui/fragments/anti_uninstall/SetupPasswordModeFragment.kt:
--------------------------------------------------------------------------------
1 | package nethical.digipaws.ui.fragments.anti_uninstall
2 |
3 | import android.content.Context
4 | import android.content.Intent
5 | import android.os.Bundle
6 | import android.view.LayoutInflater
7 | import android.view.View
8 | import android.view.ViewGroup
9 | import androidx.fragment.app.Fragment
10 | import com.google.android.material.dialog.MaterialAlertDialogBuilder
11 | import nethical.digipaws.Constants
12 | import nethical.digipaws.R
13 | import nethical.digipaws.databinding.FragmentSetupPasswordModeBinding
14 | import nethical.digipaws.services.GeneralFeaturesService
15 |
16 | class SetupPasswordModeFragment : Fragment() {
17 |
18 | private var _binding: FragmentSetupPasswordModeBinding? = null
19 | private val binding get() = _binding!! // Safe getter for binding
20 |
21 | override fun onCreateView(
22 | inflater: LayoutInflater,
23 | container: ViewGroup?,
24 | savedInstanceState: Bundle?
25 | ): View {
26 | _binding = FragmentSetupPasswordModeBinding.inflate(inflater, container, false)
27 | return binding.root
28 | }
29 |
30 | override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
31 | super.onViewCreated(view, savedInstanceState)
32 |
33 | binding.btnNextPass.setOnClickListener {
34 | MaterialAlertDialogBuilder(requireContext())
35 | .setTitle(getString(R.string.alert))
36 | .setMessage(getString(R.string.are_you_sure_you_want_to_turn_on_anti_uninstall_there_is_no_turning_back))
37 |
38 | .setPositiveButton(getString(R.string.i_understand)) { _, _ ->
39 | setupPasswordMode()
40 | }
41 | .setNegativeButton(getString(R.string.cancel)) { _, dialog ->
42 | requireActivity().finish()
43 | }
44 | .show()
45 | }
46 |
47 | binding.cbBlockChanges.setOnCheckedChangeListener { _, isChecked ->
48 | if (isChecked) {
49 | MaterialAlertDialogBuilder(requireContext())
50 | .setTitle(getString(R.string.alert))
51 | .setMessage(getString(R.string.if_you_enable_this_you_won_t_be_able_to_change_configurations_such_as_adding_blocked_apps_keywords_and_more))
52 | .setPositiveButton(getString(R.string.i_understand), null)
53 | .show()
54 | }
55 | }
56 |
57 | }
58 |
59 | private fun setupPasswordMode() {
60 | val editor =
61 | activity?.getSharedPreferences("anti_uninstall", Context.MODE_PRIVATE)?.edit()
62 | editor?.apply() {
63 | putBoolean("is_anti_uninstall_on", true)
64 | putString("password", binding.password.text.toString())
65 | putInt("mode", Constants.ANTI_UNINSTALL_PASSWORD_MODE)
66 | putBoolean("is_configuring_blocked", binding.cbBlockChanges.isChecked)
67 | commit()
68 | }
69 |
70 | val intent = Intent(GeneralFeaturesService.INTENT_ACTION_REFRESH_ANTI_UNINSTALL)
71 | activity?.sendBroadcast(intent)
72 |
73 | activity?.finish()
74 | }
75 |
76 | override fun onDestroyView() {
77 | super.onDestroyView()
78 | _binding = null
79 | }
80 | }
--------------------------------------------------------------------------------
/app/src/main/java/nethical/digipaws/ui/fragments/anti_uninstall/SetupTimedModeFragment.kt:
--------------------------------------------------------------------------------
1 | package nethical.digipaws.ui.fragments.anti_uninstall
2 |
3 | import android.content.Context
4 | import android.content.Intent
5 | import android.os.Bundle
6 | import android.view.LayoutInflater
7 | import android.view.View
8 | import android.view.ViewGroup
9 | import androidx.fragment.app.Fragment
10 | import com.google.android.material.dialog.MaterialAlertDialogBuilder
11 | import nethical.digipaws.Constants
12 | import nethical.digipaws.R
13 | import nethical.digipaws.databinding.FragmentSetupTimedModeBinding
14 | import nethical.digipaws.services.GeneralFeaturesService
15 |
16 | class SetupTimedModeFragment : Fragment() {
17 |
18 | private var _binding: FragmentSetupTimedModeBinding? = null
19 | private val binding get() = _binding!! // Safe getter for binding
20 |
21 | override fun onCreateView(
22 | inflater: LayoutInflater,
23 | container: ViewGroup?,
24 | savedInstanceState: Bundle?
25 | ): View {
26 | _binding = FragmentSetupTimedModeBinding.inflate(inflater, container, false)
27 | return binding.root
28 | }
29 |
30 | override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
31 | super.onViewCreated(view, savedInstanceState)
32 | binding.calendarView.minDate = binding.calendarView.date
33 | var selectedDate: String? = null
34 | binding.calendarView.setOnDateChangeListener { _, year, month, dayOfMonth ->
35 | selectedDate = "${month + 1}/$dayOfMonth/$year"
36 | }
37 | binding.turnOnTimed.setOnClickListener {
38 |
39 | MaterialAlertDialogBuilder(requireContext())
40 | .setTitle(getString(R.string.alert))
41 | .setMessage(getString(R.string.are_you_sure_you_want_to_turn_on_anti_uninstall_there_is_no_turning_back))
42 | .setPositiveButton(getString(R.string.i_understand)) { _, dialog ->
43 | selectedDate?.let { it1 -> turnOnTimedMode(it1) }
44 | }
45 | .setNegativeButton(getString(R.string.cancel)) { _, dialog ->
46 | requireActivity().finish()
47 | }
48 | .show()
49 | }
50 | binding.blockChanges.setOnCheckedChangeListener { _, isChecked ->
51 | if (isChecked) {
52 | MaterialAlertDialogBuilder(requireContext())
53 | .setTitle(getString(R.string.alert))
54 | .setMessage(getString(R.string.if_you_enable_this_you_won_t_be_able_to_change_configurations_such_as_adding_blocked_apps_keywords_and_more))
55 | .setPositiveButton(getString(R.string.i_understand), null)
56 | .show()
57 | }
58 | }
59 |
60 | }
61 |
62 | private fun turnOnTimedMode(selectedDate: String) {
63 |
64 | val editor =
65 | activity?.getSharedPreferences("anti_uninstall", Context.MODE_PRIVATE)?.edit()
66 | editor?.apply() {
67 | putBoolean("is_anti_uninstall_on", true)
68 | putString("date", selectedDate)
69 | putBoolean("is_configuring_blocked", binding.blockChanges.isChecked)
70 | putInt("mode", Constants.ANTI_UNINSTALL_TIMED_MODE)
71 | commit()
72 | }
73 |
74 | val intent = Intent(GeneralFeaturesService.INTENT_ACTION_REFRESH_ANTI_UNINSTALL)
75 | activity?.sendBroadcast(intent)
76 |
77 | activity?.finish()
78 | }
79 |
80 | override fun onDestroyView() {
81 | super.onDestroyView()
82 | _binding = null
83 | }
84 | }
--------------------------------------------------------------------------------
/app/src/main/java/nethical/digipaws/ui/fragments/installation/AccessibilityGuide.kt:
--------------------------------------------------------------------------------
1 | package nethical.digipaws.ui.fragments.installation
2 |
3 | import android.annotation.SuppressLint
4 | import android.os.Bundle
5 | import android.view.LayoutInflater
6 | import android.view.View
7 | import android.view.ViewGroup
8 | import androidx.fragment.app.Fragment
9 | import nethical.digipaws.databinding.FragmentAccessibilityGuideBinding
10 |
11 | class AccessibilityGuide : Fragment() {
12 | companion object {
13 | const val FRAGMENT_ID = "accessibility_guide_fragment"
14 | }
15 |
16 | private var _binding: FragmentAccessibilityGuideBinding? = null
17 | private val binding get() = _binding!! // Safe getter for binding
18 |
19 | override fun onCreateView(
20 | inflater: LayoutInflater,
21 | container: ViewGroup?,
22 | savedInstanceState: Bundle?
23 | ): View {
24 | _binding = FragmentAccessibilityGuideBinding.inflate(inflater, container, false)
25 | return binding.root
26 | }
27 |
28 | @SuppressLint("CommitPrefEdits")
29 | override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
30 | super.onViewCreated(view, savedInstanceState)
31 | binding.btnNext.setOnClickListener {
32 | requireActivity().finish()
33 | }
34 | }
35 |
36 |
37 | override fun onDestroyView() {
38 | super.onDestroyView()
39 | _binding = null
40 | }
41 | }
--------------------------------------------------------------------------------
/app/src/main/java/nethical/digipaws/ui/fragments/installation/WelcomeFragment.kt:
--------------------------------------------------------------------------------
1 | package nethical.digipaws.ui.fragments.installation
2 |
3 | import android.content.ActivityNotFoundException
4 | import android.content.Intent
5 | import android.net.Uri
6 | import android.os.Bundle
7 | import android.view.LayoutInflater
8 | import android.view.View
9 | import android.view.ViewGroup
10 | import android.widget.Toast
11 | import androidx.fragment.app.Fragment
12 | import nethical.digipaws.R
13 | import nethical.digipaws.databinding.FragmentWelcomeBinding
14 |
15 | class WelcomeFragment : Fragment() {
16 |
17 | companion object {
18 | const val FRAGMENT_ID = "welcome_fragment"
19 | }
20 |
21 | private var _binding: FragmentWelcomeBinding? = null
22 | private val binding get() = _binding!! // Safe getter for binding
23 |
24 | override fun onCreateView(
25 | inflater: LayoutInflater,
26 | container: ViewGroup?,
27 | savedInstanceState: Bundle?
28 | ): View {
29 | _binding = FragmentWelcomeBinding.inflate(inflater, container, false)
30 | return binding.root
31 | }
32 |
33 | override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
34 | super.onViewCreated(view, savedInstanceState)
35 |
36 | binding.cbTos.setOnCheckedChangeListener { _, isChecked ->
37 | binding.btnNext.isEnabled = isChecked
38 | }
39 | binding.openTos.setOnClickListener {
40 | val intent =
41 | Intent(Intent.ACTION_VIEW, Uri.parse("https://digipaws.life/terms-and-conditions"))
42 | try {
43 | startActivity(intent)
44 | } catch (e: ActivityNotFoundException) {
45 | Toast.makeText(
46 | requireContext(),
47 | "No application found to open the link",
48 | Toast.LENGTH_SHORT
49 | ).show()
50 | }
51 | }
52 |
53 | binding.btnNext.setOnClickListener {
54 | requireActivity().supportFragmentManager.beginTransaction()
55 | .replace(
56 | R.id.fragment_holder,
57 | PermissionsFragment()
58 | ) // Replace with FragmentB
59 | .addToBackStack(null)
60 | .commit()
61 | }
62 | }
63 |
64 |
65 | override fun onDestroyView() {
66 | super.onDestroyView()
67 | _binding = null
68 | }
69 | }
--------------------------------------------------------------------------------
/app/src/main/java/nethical/digipaws/ui/overlay/UsageStatOverlayManager.kt:
--------------------------------------------------------------------------------
1 | package nethical.digipaws.ui.overlay
2 |
3 | import android.annotation.SuppressLint
4 | import android.content.Context
5 | import android.graphics.PixelFormat
6 | import android.os.CountDownTimer
7 | import android.util.Log
8 | import android.view.Gravity
9 | import android.view.LayoutInflater
10 | import android.view.View
11 | import android.view.WindowManager
12 | import android.view.WindowManager.LayoutParams
13 | import nethical.digipaws.databinding.OverlayUsageStatBinding
14 |
15 | class UsageStatOverlayManager(private val context: Context) {
16 |
17 | private var overlayView: View? = null
18 | var binding: OverlayUsageStatBinding? = null
19 | var isOverlayVisible = false
20 | private var windowManager: WindowManager? = null
21 |
22 | var reelsScrolledThisSession = 0
23 |
24 | @SuppressLint("InlinedApi")
25 | fun startDisplaying() {
26 | if (overlayView != null || isOverlayVisible) return
27 |
28 | binding = OverlayUsageStatBinding.inflate(LayoutInflater.from(context))
29 | isOverlayVisible = true
30 | overlayView = binding?.root
31 |
32 | // Set up WindowManager.LayoutParams for the overlay
33 | val layoutParams = LayoutParams(
34 | LayoutParams.MATCH_PARENT,
35 | LayoutParams.MATCH_PARENT,
36 | LayoutParams.TYPE_ACCESSIBILITY_OVERLAY,
37 | LayoutParams.FLAG_NOT_FOCUSABLE or
38 | LayoutParams.FLAG_NOT_TOUCHABLE or
39 | LayoutParams.FLAG_LAYOUT_IN_SCREEN
40 | or LayoutParams.FLAG_LAYOUT_NO_LIMITS,
41 | PixelFormat.TRANSLUCENT
42 | )
43 | layoutParams.gravity = Gravity.CENTER
44 | layoutParams.layoutInDisplayCutoutMode = LayoutParams.LAYOUT_IN_DISPLAY_CUTOUT_MODE_SHORT_EDGES
45 |
46 | windowManager = context.getSystemService(Context.WINDOW_SERVICE) as WindowManager
47 |
48 | windowManager?.addView(overlayView, layoutParams)
49 | }
50 |
51 | fun removeOverlay() {
52 | if (overlayView != null && windowManager != null) {
53 | Log.d("UsageStatOverlayManager", "Removing overlay.")
54 | windowManager?.removeView(overlayView)
55 | overlayView = null
56 | binding = null
57 | isOverlayVisible = false
58 | } else {
59 | Log.d("UsageStatOverlayManager", "No overlay to remove.")
60 | }
61 | }
62 |
63 | }
--------------------------------------------------------------------------------
/app/src/main/java/nethical/digipaws/utils/AnimTools.kt:
--------------------------------------------------------------------------------
1 | package nethical.digipaws.utils
2 |
3 | import android.view.View
4 |
5 | class AnimTools {
6 | companion object {
7 | fun View.animateVisibility(show: Boolean, duration: Long = 300) {
8 | if (show) {
9 | this.apply {
10 | alpha = 0f
11 | visibility = View.VISIBLE
12 | animate().alpha(1f).setDuration(duration).start()
13 | }
14 | } else {
15 | this.animate()
16 | .alpha(0f)
17 | .setDuration(duration)
18 | .withEndAction { visibility = View.GONE }
19 | .start()
20 | }
21 | }
22 | }
23 | }
--------------------------------------------------------------------------------
/app/src/main/java/nethical/digipaws/utils/CommonUtils.kt:
--------------------------------------------------------------------------------
1 | package nethical.digipaws.utils
2 |
3 | import android.content.Context
4 | import android.content.Intent
5 | import android.content.pm.PackageManager
6 | import android.view.inputmethod.InputMethodManager
7 |
8 |
9 | fun getDefaultLauncherPackageName(packageManager: PackageManager): String? {
10 | val intent = Intent(Intent.ACTION_MAIN).apply {
11 | addCategory(Intent.CATEGORY_HOME)
12 | }
13 |
14 | val resolveInfo = packageManager.resolveActivity(intent, PackageManager.MATCH_DEFAULT_ONLY)
15 | return resolveInfo?.activityInfo?.packageName
16 | }
17 |
18 |
19 | fun getCurrentKeyboardPackageName(context: Context): String? {
20 | context.getSystemService(Context.INPUT_METHOD_SERVICE) as InputMethodManager
21 | val currentInputMethodId = android.provider.Settings.Secure.getString(
22 | context.contentResolver,
23 | android.provider.Settings.Secure.DEFAULT_INPUT_METHOD
24 | )
25 | return currentInputMethodId?.substringBefore('/')
26 | }
27 |
--------------------------------------------------------------------------------
/app/src/main/java/nethical/digipaws/utils/MonochromeTools.kt:
--------------------------------------------------------------------------------
1 | package nethical.digipaws.utils
2 |
3 | import android.util.Log
4 |
5 | class GrayscaleControl {
6 | private val commandListener = object : ShizukuRunner.CommandResultListener {
7 | override fun onCommandResult(output: String, done: Boolean) {
8 | Log.d("monochrome output: ",output)
9 | // Handle successful command execution if needed
10 | }
11 |
12 | override fun onCommandError(error: String) {
13 | // Handle command errors if needed
14 |
15 | Log.d("monochrome error: ",error)
16 | }
17 | }
18 |
19 | /**
20 | * Enable grayscale mode
21 | */
22 | fun enableGrayscale() {
23 | ShizukuRunner.executeCommand(
24 | "settings put secure accessibility_display_daltonizer 0 && " +
25 | "settings put secure accessibility_display_daltonizer_enabled 1",
26 | commandListener
27 | )
28 | }
29 |
30 | /**
31 | * Disable grayscale mode
32 | */
33 | fun disableGrayscale() {
34 | ShizukuRunner.executeCommand(
35 | "settings put secure accessibility_display_daltonizer_enabled 0",
36 | commandListener
37 | )
38 | }
39 |
40 | /**
41 | * Toggle grayscale mode based on current state
42 | */
43 | fun toggleGrayscale() {
44 | ShizukuRunner.executeCommand(
45 | "if [ $(settings get secure accessibility_display_daltonizer_enabled) = \"1\" ]; then " +
46 | "settings put secure accessibility_display_daltonizer_enabled 0; " +
47 | "else " +
48 | "settings put secure accessibility_display_daltonizer 0 && " +
49 | "settings put secure accessibility_display_daltonizer_enabled 1; " +
50 | "fi",
51 | commandListener
52 | )
53 | }
54 |
55 | /**
56 | * Get current grayscale state
57 | * @param callback Lambda that receives the current state (true if enabled, false if disabled)
58 | */
59 | fun isGrayscaleEnabled(callback: (Boolean) -> Unit) {
60 | ShizukuRunner.executeCommand(
61 | "settings get secure accessibility_display_daltonizer_enabled",
62 | object : ShizukuRunner.CommandResultListener {
63 | override fun onCommandResult(output: String, done: Boolean) {
64 | if (done) {
65 | callback(output.trim() == "1")
66 | }
67 | }
68 |
69 | override fun onCommandError(error: String) {
70 | callback(false)
71 | }
72 | }
73 | )
74 | }
75 | }
--------------------------------------------------------------------------------
/app/src/main/java/nethical/digipaws/utils/NotificationTimerManager.kt:
--------------------------------------------------------------------------------
1 | package nethical.digipaws.utils
2 |
3 |
4 | import android.app.NotificationChannel
5 | import android.app.NotificationManager
6 | import android.content.Context
7 | import android.os.CountDownTimer
8 | import androidx.core.app.NotificationCompat
9 |
10 | class NotificationTimerManager(private val context: Context) {
11 |
12 | companion object {
13 | private const val CHANNEL_ID = "TimerNotificationChannel"
14 | private const val NOTIFICATION_ID = 1001
15 | }
16 |
17 | private var countDownTimer: CountDownTimer? = null
18 | private val notificationManager: NotificationManager by lazy {
19 | context.getSystemService(Context.NOTIFICATION_SERVICE) as NotificationManager
20 | }
21 |
22 | init {
23 | createNotificationChannel()
24 | }
25 |
26 | private fun createNotificationChannel() {
27 | val channel = NotificationChannel(
28 | CHANNEL_ID,
29 | "Timer Notifications",
30 | NotificationManager.IMPORTANCE_LOW
31 | ).apply {
32 | description = "Timer progress notifications"
33 | setSound(null, null)
34 | enableVibration(false)
35 | }
36 | notificationManager.createNotificationChannel(channel)
37 | }
38 |
39 | fun startTimer(
40 | totalMillis: Long,
41 | isCountdown: Boolean = true,
42 | onTickCallback: ((Long) -> Unit)? = null,
43 | onFinishCallback: (() -> Unit)? = null
44 | ) {
45 | // Cancel any existing timer
46 | countDownTimer?.cancel()
47 |
48 | countDownTimer = object : CountDownTimer(totalMillis, 1000) {
49 | override fun onTick(millisUntilFinished: Long) {
50 | val displayMillis =
51 | if (isCountdown) millisUntilFinished else totalMillis - millisUntilFinished
52 |
53 | // Update notification with current timer value
54 | updateTimerNotification(displayMillis)
55 |
56 | // Optional tick callback
57 | onTickCallback?.invoke(displayMillis)
58 | }
59 |
60 | override fun onFinish() {
61 | // Remove the notification when timer completes
62 | notificationManager.cancel(NOTIFICATION_ID)
63 |
64 | // Optional finish callback
65 | onFinishCallback?.invoke()
66 | }
67 | }.start()
68 | }
69 |
70 | private fun updateTimerNotification(remainingMillis: Long) {
71 | // Convert milliseconds to hours, minutes, seconds
72 | val hours = remainingMillis / 3600000
73 | val minutes = (remainingMillis % 3600000) / 60000
74 | val seconds = (remainingMillis % 60000) / 1000
75 |
76 | // Format time display
77 | val timeString = String.format("%02d:%02d:%02d", hours, minutes, seconds)
78 |
79 | val notification = NotificationCompat.Builder(context, CHANNEL_ID)
80 | .setContentTitle("Timer")
81 | .setContentText(timeString)
82 | .setSmallIcon(android.R.drawable.ic_dialog_info)
83 | .setPriority(NotificationCompat.PRIORITY_LOW)
84 | .setOngoing(true) // Makes notification persistent
85 | .setCategory(NotificationCompat.CATEGORY_PROGRESS)
86 | .setVisibility(NotificationCompat.VISIBILITY_PUBLIC)
87 | .build()
88 |
89 | notificationManager.notify(NOTIFICATION_ID, notification)
90 | }
91 |
92 | fun stopTimer() {
93 | countDownTimer?.cancel()
94 | notificationManager.cancel(NOTIFICATION_ID)
95 | }
96 | }
--------------------------------------------------------------------------------
/app/src/main/java/nethical/digipaws/utils/ShizukuRunner.kt:
--------------------------------------------------------------------------------
1 | package nethical.digipaws.utils
2 | import moe.shizuku.server.IShizukuService
3 | import rikka.shizuku.Shizuku
4 | import java.io.BufferedReader
5 | import java.io.FileInputStream
6 | import java.io.InputStreamReader
7 |
8 | /**
9 | * Utility class for executing shell commands using Shizuku
10 | */
11 | class ShizukuRunner {
12 |
13 | interface CommandResultListener {
14 | /**
15 | * Called when the command produces output.
16 | * @param output The output from the command execution
17 | * @param done True if the command execution is complete, false otherwise
18 | */
19 | fun onCommandResult(output: String, done: Boolean) {}
20 |
21 | /**
22 | * Called when an error occurs during command execution.
23 | * @param error The error message
24 | */
25 | fun onCommandError(error: String) {}
26 | }
27 |
28 | companion object {
29 |
30 | /**
31 | * Executes a shell command using Shizuku.
32 | * @param command The shell command to execute
33 | * @param listener Listener for command results and errors
34 | * @param lineBundle Number of lines to batch before invoking the listener
35 | */
36 | fun executeCommand(command: String, listener: CommandResultListener, lineBundle: Int = 50) {
37 | Thread {
38 | try {
39 | // Initialize the Shizuku process
40 | val process = IShizukuService.Stub.asInterface(Shizuku.getBinder())
41 | .newProcess(arrayOf("sh", "-c", command), null, null)
42 |
43 | // Readers for command output and error streams
44 | val outputReader = BufferedReader(InputStreamReader(FileInputStream(process.inputStream.fileDescriptor)))
45 | val errorReader = BufferedReader(InputStreamReader(FileInputStream(process.errorStream.fileDescriptor)))
46 |
47 | // StringBuilders for capturing output and errors
48 | val outputBuffer = StringBuilder()
49 | val errorBuffer = StringBuilder()
50 |
51 | var line: String?
52 | var lineCount = 0
53 |
54 | // Read output stream
55 | while (outputReader.readLine().also { line = it } != null) {
56 | lineCount++
57 | outputBuffer.append(line).append("\n")
58 |
59 | // Send partial results if lineBundle is reached
60 | if (lineCount == lineBundle) {
61 | lineCount = 0
62 | listener.onCommandResult(outputBuffer.toString(), false)
63 | outputBuffer.clear()
64 | }
65 | }
66 |
67 | // Read error stream
68 | while (errorReader.readLine().also { line = it } != null) {
69 | errorBuffer.append(line).append("\n")
70 | }
71 |
72 | // Notify listener of results or errors
73 | if (errorBuffer.isNotBlank()) {
74 | listener.onCommandError(errorBuffer.toString())
75 | } else {
76 | listener.onCommandResult(outputBuffer.toString(), true)
77 | }
78 |
79 | // Wait for process to complete
80 | process.waitFor()
81 |
82 | } catch (e: Exception) {
83 | // Notify listener of exceptions
84 | listener.onCommandError(e.message ?: "An unexpected error occurred while executing the command.")
85 | }
86 | }.start()
87 | }
88 | }
89 | }
90 |
--------------------------------------------------------------------------------
/app/src/main/java/nethical/digipaws/utils/TimeTools.kt:
--------------------------------------------------------------------------------
1 | package nethical.digipaws.utils
2 |
3 | import java.text.SimpleDateFormat
4 | import java.time.LocalDate
5 | import java.time.LocalTime
6 | import java.time.format.DateTimeFormatter
7 | import java.util.Date
8 | import java.util.Locale
9 |
10 | class TimeTools {
11 | companion object {
12 | fun convertToMinutesFromMidnight(hour: Int, minute: Int): Int {
13 | return (hour * 60) + minute
14 | }
15 |
16 | fun convertMinutesTo24Hour(minutes: Int): Pair {
17 | return Pair(minutes / 60, minutes % 60)
18 | }
19 |
20 | fun getCurrentDate(): String {
21 | val currentDate = LocalDate.now()
22 |
23 | val formatter = DateTimeFormatter.ofPattern("dd MMMM yyyy")
24 | return currentDate.format(formatter)
25 | }
26 | fun getPreviousDate(daysAgo:Long = 1): String {
27 | val previousDate = LocalDate.now().minusDays(daysAgo)
28 |
29 | val formatter = DateTimeFormatter.ofPattern("dd MMMM yyyy")
30 | return previousDate.format(formatter)
31 | }
32 |
33 |
34 | fun getCurrentTime(): String {
35 | val currentTime = LocalTime.now()
36 |
37 | val formatter = DateTimeFormatter.ofPattern("HH:mm:ss")
38 |
39 | return currentTime.format(formatter)
40 | }
41 |
42 | fun shortenDate(dateString: String): String {
43 | val parts = dateString.split(" ")
44 |
45 | if (parts.size >= 2) {
46 | val day = parts[0]
47 | val month = parts[1].take(3)
48 | return "$day $month"
49 | }
50 |
51 | return dateString
52 | }
53 |
54 | fun formatDate(timestamp: Long): String {
55 | val dateFormat = SimpleDateFormat("MMM dd, yyyy", Locale.getDefault())
56 | return dateFormat.format(Date(timestamp))
57 | }
58 |
59 |
60 |
61 | fun formatTime(timeInMillis: Long, showSeconds: Boolean = true): String {
62 | val hours = timeInMillis / (1000 * 60 * 60)
63 | val minutes = (timeInMillis % (1000 * 60 * 60)) / (1000 * 60)
64 | val seconds = (timeInMillis % (1000 * 60)) / 1000
65 |
66 | return buildString {
67 | if (hours > 0) append("$hours hr")
68 | if (minutes > 0) append(" $minutes mins")
69 | if (showSeconds && seconds > 0) append(" $seconds secs")
70 | }.trim()
71 | }
72 |
73 | fun formatTimeForWidget(timeInMillis: Long): String {
74 | val hours = timeInMillis / (1000 * 60 * 60)
75 | val minutes = (timeInMillis % (1000 * 60 * 60)) / (1000 * 60)
76 |
77 | return buildString {
78 | if (hours > 0) append("${hours}h")
79 | if (minutes > 0L) append("${minutes}m")
80 | if (hours == 0L && minutes == 0L) append("<1m") // Handle case for less than 1 minute
81 | }.trim()
82 | }
83 |
84 |
85 | }
86 | }
--------------------------------------------------------------------------------
/app/src/main/java/nethical/digipaws/views/CustomMarkerView.kt:
--------------------------------------------------------------------------------
1 | package nethical.digipaws.views
2 |
3 | import android.content.Context
4 | import android.widget.TextView
5 | import com.github.mikephil.charting.components.MarkerView
6 | import com.github.mikephil.charting.data.Entry
7 | import com.github.mikephil.charting.highlight.Highlight
8 | import com.github.mikephil.charting.utils.MPPointF
9 | import nethical.digipaws.R
10 |
11 | class CustomMarkerView(context: Context, layoutResource: Int) :
12 | MarkerView(context, layoutResource) {
13 |
14 | private val tvContent: TextView = findViewById(R.id.tvContent)
15 |
16 | var showDecimal = true
17 | override fun refreshContent(e: Entry?, highlight: Highlight?) {
18 | e?.y?.let { value ->
19 | tvContent.text = if (showDecimal) {
20 | "%.2f".format(value) // Efficient decimal formatting
21 | } else {
22 | value.toInt().toString() // Convert to integer for non-decimal
23 | }
24 | }
25 |
26 | super.refreshContent(e, highlight)
27 | }
28 |
29 | override fun getOffset(): MPPointF {
30 | // Adjust marker position
31 | return MPPointF(-(width / 2).toFloat(), -height.toFloat())
32 | }
33 | }
34 |
--------------------------------------------------------------------------------
/app/src/main/res/anim/fade_in.xml:
--------------------------------------------------------------------------------
1 |
2 |
6 |
--------------------------------------------------------------------------------
/app/src/main/res/anim/fade_out.xml:
--------------------------------------------------------------------------------
1 |
2 |
6 |
--------------------------------------------------------------------------------
/app/src/main/res/drawable/baseline_add_24.xml:
--------------------------------------------------------------------------------
1 |
7 |
8 |
11 |
12 |
13 |
--------------------------------------------------------------------------------
/app/src/main/res/drawable/baseline_android_24.xml:
--------------------------------------------------------------------------------
1 |
7 |
8 |
11 |
12 |
13 |
--------------------------------------------------------------------------------
/app/src/main/res/drawable/baseline_app_shortcut_24.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
8 |
9 |
10 |
11 |
12 |
--------------------------------------------------------------------------------
/app/src/main/res/drawable/baseline_auto_fix_high_24.xml:
--------------------------------------------------------------------------------
1 |
7 |
8 |
11 |
12 |
13 |
--------------------------------------------------------------------------------
/app/src/main/res/drawable/baseline_close_24.xml:
--------------------------------------------------------------------------------
1 |
7 |
8 |
11 |
12 |
13 |
--------------------------------------------------------------------------------
/app/src/main/res/drawable/baseline_date_range_24.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
--------------------------------------------------------------------------------
/app/src/main/res/drawable/baseline_delete_24.xml:
--------------------------------------------------------------------------------
1 |
7 |
8 |
11 |
12 |
13 |
--------------------------------------------------------------------------------
/app/src/main/res/drawable/baseline_done_24.xml:
--------------------------------------------------------------------------------
1 |
7 |
8 |
11 |
12 |
13 |
--------------------------------------------------------------------------------
/app/src/main/res/drawable/baseline_edit_24.xml:
--------------------------------------------------------------------------------
1 |
7 |
8 |
11 |
12 |
13 |
--------------------------------------------------------------------------------
/app/src/main/res/drawable/baseline_help_outline_24.xml:
--------------------------------------------------------------------------------
1 |
8 |
9 |
12 |
13 |
14 |
--------------------------------------------------------------------------------
/app/src/main/res/drawable/baseline_info_24.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
--------------------------------------------------------------------------------
/app/src/main/res/drawable/baseline_lock_24.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
--------------------------------------------------------------------------------
/app/src/main/res/drawable/baseline_query_stats_24.xml:
--------------------------------------------------------------------------------
1 |
7 |
8 |
11 |
12 |
13 |
--------------------------------------------------------------------------------
/app/src/main/res/drawable/baseline_read_more_24.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
8 |
9 |
10 |
11 |
12 |
--------------------------------------------------------------------------------
/app/src/main/res/drawable/baseline_refresh_24.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
--------------------------------------------------------------------------------
/app/src/main/res/drawable/baseline_remove_24.xml:
--------------------------------------------------------------------------------
1 |
7 |
8 |
11 |
12 |
13 |
--------------------------------------------------------------------------------
/app/src/main/res/drawable/baseline_share_24.xml:
--------------------------------------------------------------------------------
1 |
7 |
8 |
11 |
12 |
13 |
--------------------------------------------------------------------------------
/app/src/main/res/drawable/baseline_start_24.xml:
--------------------------------------------------------------------------------
1 |
7 |
8 |
11 |
12 |
13 |
--------------------------------------------------------------------------------
/app/src/main/res/drawable/baseline_stop_24.xml:
--------------------------------------------------------------------------------
1 |
7 |
8 |
11 |
12 |
13 |
--------------------------------------------------------------------------------
/app/src/main/res/drawable/baseline_task_alt_24.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
--------------------------------------------------------------------------------
/app/src/main/res/drawable/baseline_warning_24.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
--------------------------------------------------------------------------------
/app/src/main/res/drawable/focus_mode_icon.jpg:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/nethical6/digipaws/3e57d27af103a2421ec70d3f96da212acaaec47b/app/src/main/res/drawable/focus_mode_icon.jpg
--------------------------------------------------------------------------------
/app/src/main/res/drawable/ic_launcher_background.xml:
--------------------------------------------------------------------------------
1 |
8 |
14 |
--------------------------------------------------------------------------------
/app/src/main/res/drawable/ic_launcher_foreground.xml:
--------------------------------------------------------------------------------
1 |
8 |
13 |
18 |
19 |
--------------------------------------------------------------------------------
/app/src/main/res/drawable/ic_launcher_monochrome.xml:
--------------------------------------------------------------------------------
1 |
8 |
13 |
18 |
19 |
--------------------------------------------------------------------------------
/app/src/main/res/drawable/widget_background.xml:
--------------------------------------------------------------------------------
1 |
2 |
4 |
5 |
6 |
7 |
8 |
--------------------------------------------------------------------------------
/app/src/main/res/layout/activity_add_timed_action_activity.xml:
--------------------------------------------------------------------------------
1 |
2 |
9 |
10 |
17 |
18 |
28 |
--------------------------------------------------------------------------------
/app/src/main/res/layout/activity_fragment.xml:
--------------------------------------------------------------------------------
1 |
2 |
8 |
9 |
10 |
14 |
--------------------------------------------------------------------------------
/app/src/main/res/layout/activity_launcher.xml:
--------------------------------------------------------------------------------
1 |
2 |
11 |
12 |
17 |
18 |
27 |
28 |
36 |
37 |
44 |
45 |
52 |
53 |
54 |
--------------------------------------------------------------------------------
/app/src/main/res/layout/activity_manage_keywords.xml:
--------------------------------------------------------------------------------
1 |
2 |
10 |
11 |
20 |
21 |
22 |
23 |
33 |
34 |
44 |
--------------------------------------------------------------------------------
/app/src/main/res/layout/activity_select_apps.xml:
--------------------------------------------------------------------------------
1 |
2 |
10 |
11 |
25 |
26 |
34 |
35 |
46 |
47 |
61 |
62 |
72 |
73 |
--------------------------------------------------------------------------------
/app/src/main/res/layout/app_usage_item.xml:
--------------------------------------------------------------------------------
1 |
2 |
11 |
12 |
16 |
17 |
26 |
27 |
39 |
40 |
49 |
50 |
51 |
--------------------------------------------------------------------------------
/app/src/main/res/layout/cheat_hour_item.xml:
--------------------------------------------------------------------------------
1 |
2 |
11 |
12 |
16 |
17 |
22 |
23 |
29 |
30 |
31 |
37 |
38 |
47 |
48 |
54 |
55 |
62 |
63 |
64 |
65 |
--------------------------------------------------------------------------------
/app/src/main/res/layout/custom_marker_view.xml:
--------------------------------------------------------------------------------
1 |
2 |
8 |
9 |
14 |
15 |
20 |
21 |
22 |
--------------------------------------------------------------------------------
/app/src/main/res/layout/dialog_add_keyword.xml:
--------------------------------------------------------------------------------
1 |
2 |
6 |
7 |
12 |
13 |
19 |
20 |
21 |
--------------------------------------------------------------------------------
/app/src/main/res/layout/dialog_add_timed_action.xml:
--------------------------------------------------------------------------------
1 |
2 |
8 |
9 |
15 |
16 |
25 |
26 |
31 |
32 |
42 |
43 |
52 |
53 |
62 |
63 |
70 |
71 |
80 |
81 |
82 |
83 |
87 |
88 |
94 |
95 |
96 |
104 |
105 |
110 |
111 |
112 |
--------------------------------------------------------------------------------
/app/src/main/res/layout/dialog_config_tracker.xml:
--------------------------------------------------------------------------------
1 |
2 |
7 |
8 |
15 |
16 |
23 |
--------------------------------------------------------------------------------
/app/src/main/res/layout/dialog_focus_mode.xml:
--------------------------------------------------------------------------------
1 |
2 |
5 |
6 |
11 |
12 |
20 |
21 |
29 |
30 |
35 |
36 |
37 |
38 |
43 |
44 |
51 |
52 |
57 |
58 |
63 |
64 |
65 |
66 |
--------------------------------------------------------------------------------
/app/src/main/res/layout/dialog_grayscale.xml:
--------------------------------------------------------------------------------
1 |
2 |
7 |
8 |
16 |
17 |
22 |
23 |
30 |
31 |
36 |
37 |
42 |
43 |
48 |
49 |
54 |
55 |
--------------------------------------------------------------------------------
/app/src/main/res/layout/dialog_keyword_blocker_config.xml:
--------------------------------------------------------------------------------
1 |
2 |
7 |
8 |
16 |
17 |
21 |
22 |
27 |
28 |
29 |
35 |
36 |
42 |
43 |
49 |
50 |
--------------------------------------------------------------------------------
/app/src/main/res/layout/dialog_keyword_package.xml:
--------------------------------------------------------------------------------
1 |
2 |
7 |
8 |
13 |
14 |
--------------------------------------------------------------------------------
/app/src/main/res/layout/dialog_remove_anti_uninstall.xml:
--------------------------------------------------------------------------------
1 |
2 |
7 |
8 |
16 |
17 |
21 |
22 |
23 |
--------------------------------------------------------------------------------
/app/src/main/res/layout/dialog_warning_overlay.xml:
--------------------------------------------------------------------------------
1 |
2 |
8 |
9 |
20 |
21 |
29 |
30 |
31 |
32 |
40 |
41 |
50 |
51 |
59 |
60 |
--------------------------------------------------------------------------------
/app/src/main/res/layout/fragment_all_app_usage.xml:
--------------------------------------------------------------------------------
1 |
2 |
8 |
9 |
14 |
15 |
23 |
24 |
33 |
34 |
40 |
41 |
51 |
52 |
62 |
63 |
73 |
74 |
75 |
--------------------------------------------------------------------------------
/app/src/main/res/layout/fragment_chose_anti_uninstall_mode.xml:
--------------------------------------------------------------------------------
1 |
2 |
9 |
10 |
19 |
20 |
25 |
26 |
31 |
32 |
37 |
38 |
43 |
44 |
45 |
54 |
--------------------------------------------------------------------------------
/app/src/main/res/layout/fragment_setup_password_mode.xml:
--------------------------------------------------------------------------------
1 |
2 |
10 |
11 |
19 |
20 |
29 |
30 |
36 |
37 |
41 |
42 |
43 |
44 |
49 |
50 |
56 |
57 |
--------------------------------------------------------------------------------
/app/src/main/res/layout/fragment_setup_timed_mode.xml:
--------------------------------------------------------------------------------
1 |
2 |
10 |
11 |
12 |
20 |
21 |
30 |
31 |
35 |
36 |
41 |
42 |
48 |
--------------------------------------------------------------------------------
/app/src/main/res/layout/horizontal_number_picker.xml:
--------------------------------------------------------------------------------
1 |
2 |
10 |
11 |
20 |
21 |
28 |
29 |
42 |
43 |
51 |
52 |
53 |
54 |
55 |
64 |
--------------------------------------------------------------------------------
/app/src/main/res/layout/keyword_item.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
12 |
13 |
17 |
23 |
31 |
32 |
33 |
--------------------------------------------------------------------------------
/app/src/main/res/layout/launcher_item.xml:
--------------------------------------------------------------------------------
1 |
2 |
7 |
8 |
19 |
--------------------------------------------------------------------------------
/app/src/main/res/layout/overlay_usage_stat.xml:
--------------------------------------------------------------------------------
1 |
2 |
9 |
10 |
20 |
21 |
29 |
30 |
--------------------------------------------------------------------------------
/app/src/main/res/layout/reel_stats.xml:
--------------------------------------------------------------------------------
1 |
2 |
7 |
8 |
14 |
15 |
24 |
25 |
32 |
33 |
40 |
41 |
48 |
49 |
56 |
57 |
66 |
67 |
74 |
75 |
82 |
83 |
90 |
91 |
101 |
--------------------------------------------------------------------------------
/app/src/main/res/layout/select_apps_item.xml:
--------------------------------------------------------------------------------
1 |
2 |
10 |
11 |
17 |
18 |
25 |
26 |
30 |
31 |
--------------------------------------------------------------------------------
/app/src/main/res/layout/widget_app_stats.xml:
--------------------------------------------------------------------------------
1 |
2 |
7 |
8 |
16 |
17 |
23 |
24 |
31 |
32 |
39 |
40 |
41 |
54 |
55 |
66 |
67 |
68 |
79 |
80 |
81 |
92 |
93 |
94 |
--------------------------------------------------------------------------------
/app/src/main/res/layout/widget_reels_count.xml:
--------------------------------------------------------------------------------
1 |
2 |
7 |
8 |
15 |
16 |
22 |
23 |
30 |
31 |
38 |
39 |
40 |
53 |
54 |
65 |
66 |
67 |
--------------------------------------------------------------------------------
/app/src/main/res/menu/app_wand.xml:
--------------------------------------------------------------------------------
1 |
2 |
--------------------------------------------------------------------------------
/app/src/main/res/menu/usage_tracker_options.xml:
--------------------------------------------------------------------------------
1 |
2 |
10 |
--------------------------------------------------------------------------------
/app/src/main/res/mipmap-anydpi-v26/ic_launcher.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
--------------------------------------------------------------------------------
/app/src/main/res/mipmap-anydpi-v26/ic_launcher_round.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
--------------------------------------------------------------------------------
/app/src/main/res/mipmap-hdpi/ic_launcher.webp:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/nethical6/digipaws/3e57d27af103a2421ec70d3f96da212acaaec47b/app/src/main/res/mipmap-hdpi/ic_launcher.webp
--------------------------------------------------------------------------------
/app/src/main/res/mipmap-hdpi/ic_launcher_round.webp:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/nethical6/digipaws/3e57d27af103a2421ec70d3f96da212acaaec47b/app/src/main/res/mipmap-hdpi/ic_launcher_round.webp
--------------------------------------------------------------------------------
/app/src/main/res/mipmap-mdpi/ic_launcher.webp:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/nethical6/digipaws/3e57d27af103a2421ec70d3f96da212acaaec47b/app/src/main/res/mipmap-mdpi/ic_launcher.webp
--------------------------------------------------------------------------------
/app/src/main/res/mipmap-mdpi/ic_launcher_round.webp:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/nethical6/digipaws/3e57d27af103a2421ec70d3f96da212acaaec47b/app/src/main/res/mipmap-mdpi/ic_launcher_round.webp
--------------------------------------------------------------------------------
/app/src/main/res/mipmap-xhdpi/ic_launcher.webp:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/nethical6/digipaws/3e57d27af103a2421ec70d3f96da212acaaec47b/app/src/main/res/mipmap-xhdpi/ic_launcher.webp
--------------------------------------------------------------------------------
/app/src/main/res/mipmap-xhdpi/ic_launcher_round.webp:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/nethical6/digipaws/3e57d27af103a2421ec70d3f96da212acaaec47b/app/src/main/res/mipmap-xhdpi/ic_launcher_round.webp
--------------------------------------------------------------------------------
/app/src/main/res/mipmap-xxhdpi/ic_launcher.webp:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/nethical6/digipaws/3e57d27af103a2421ec70d3f96da212acaaec47b/app/src/main/res/mipmap-xxhdpi/ic_launcher.webp
--------------------------------------------------------------------------------
/app/src/main/res/mipmap-xxhdpi/ic_launcher_round.webp:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/nethical6/digipaws/3e57d27af103a2421ec70d3f96da212acaaec47b/app/src/main/res/mipmap-xxhdpi/ic_launcher_round.webp
--------------------------------------------------------------------------------
/app/src/main/res/mipmap-xxxhdpi/ic_launcher.webp:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/nethical6/digipaws/3e57d27af103a2421ec70d3f96da212acaaec47b/app/src/main/res/mipmap-xxxhdpi/ic_launcher.webp
--------------------------------------------------------------------------------
/app/src/main/res/mipmap-xxxhdpi/ic_launcher_round.webp:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/nethical6/digipaws/3e57d27af103a2421ec70d3f96da212acaaec47b/app/src/main/res/mipmap-xxxhdpi/ic_launcher_round.webp
--------------------------------------------------------------------------------
/app/src/main/res/values-de/strings.xml:
--------------------------------------------------------------------------------
1 |
2 |
--------------------------------------------------------------------------------
/app/src/main/res/values-hi/strings.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 | ऐप प्रतिबंधक
4 | लॉन्चर विकल्प
5 | ध्यान मोड
6 | शब्द प्रतिबंधक
7 | अन्इंस्टॉल प्रतिबंधक
8 | उपयोग निगरानी
9 | व्यू/रील प्रतिबंधक
10 |
--------------------------------------------------------------------------------
/app/src/main/res/values-night/themes.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
52 |
53 |
--------------------------------------------------------------------------------
/app/src/main/res/values/themes.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
51 |
52 |
53 |
54 |
60 |
61 |
--------------------------------------------------------------------------------
/app/src/main/res/xml/app_blocker_service_config.xml:
--------------------------------------------------------------------------------
1 |
2 |
--------------------------------------------------------------------------------
/app/src/main/res/xml/device_admin_receiver_info.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
--------------------------------------------------------------------------------
/app/src/main/res/xml/file_paths.xml:
--------------------------------------------------------------------------------
1 |
2 |
5 |
6 |
9 |
10 |
--------------------------------------------------------------------------------
/app/src/main/res/xml/general_features_service_config.xml:
--------------------------------------------------------------------------------
1 |
2 |
--------------------------------------------------------------------------------
/app/src/main/res/xml/keyword_blocker_service_config.xml:
--------------------------------------------------------------------------------
1 |
2 |
--------------------------------------------------------------------------------
/app/src/main/res/xml/shortcuts.xml:
--------------------------------------------------------------------------------
1 |
2 |
9 |
13 |
14 |
15 |
16 |
17 |
18 |
--------------------------------------------------------------------------------
/app/src/main/res/xml/usage_tracker_service_config.xml:
--------------------------------------------------------------------------------
1 |
2 |
--------------------------------------------------------------------------------
/app/src/main/res/xml/view_blocker_service_config.xml:
--------------------------------------------------------------------------------
1 |
2 |
--------------------------------------------------------------------------------
/app/src/main/res/xml/widget_reels_metadata.xml:
--------------------------------------------------------------------------------
1 |
2 |
14 |
15 |
--------------------------------------------------------------------------------
/app/src/main/res/xml/widget_screentime_metadata.xml:
--------------------------------------------------------------------------------
1 |
2 |
13 |
--------------------------------------------------------------------------------
/app/src/test/java/nethical/digipaws/ExampleUnitTest.kt:
--------------------------------------------------------------------------------
1 | package nethical.digipaws
2 |
3 | import org.junit.Test
4 |
5 | import org.junit.Assert.*
6 |
7 | /**
8 | * Example local unit test, which will execute on the development machine (host).
9 | *
10 | * See [testing documentation](http://d.android.com/tools/testing).
11 | */
12 | class ExampleUnitTest {
13 | @Test
14 | fun addition_isCorrect() {
15 | assertEquals(4, 2 + 2)
16 | }
17 | }
--------------------------------------------------------------------------------
/build.gradle.kts:
--------------------------------------------------------------------------------
1 | // Top-level build file where you can add configuration options common to all sub-projects/modules.
2 | plugins {
3 | alias(libs.plugins.android.application) apply false
4 | alias(libs.plugins.kotlin.android) apply false
5 | }
--------------------------------------------------------------------------------
/fastlane/metadata/android/en-US/full_description.txt:
--------------------------------------------------------------------------------
1 | Tired of losing hours to endless scrolling and Screen Time? Meet DigiPaws – the ultimate app to cure screen addiction!
2 |
3 |
4 | Features
5 |
6 | - Versatile Blockers: Everything Packed in a single app
7 | - App Blocker : Block apps, set cheat hours, and more
8 | - Keyword Blocker : Block custom keywords, Porn blocker , and setup custom link redirection
9 | - In App blocker : Block youtube shorts or instagram reels while allowing access to other app features
10 |
11 | - Grayscale Filter : Turn specific apps black and white to make them boring
12 |
13 | - Reels Tracker : Track the number of Intagram reels or tiktoks you scroll
14 | - App Usage Tracker : Track how you are spending your time on phone
15 | - Homescreen Widgets : Display statistics on your homescreen
16 |
17 | - Anti-Uninstall: Put password or block uninstallation for a few days
18 |
19 | - Focus Mode : Block all apps except the whitelisted ones or vice versa
20 | - Auto Focus : Automatically block selected apps at specified hours of the day
21 |
22 | - Extremely Customisable : Setup custom warning messages, cooldowns, and more
23 | - Privacy Oriented : Everything stays secure on your local device without the need of INTERNET permission
24 | Usage
25 |
26 | 1. Launch DigiPaws on your Android device.
27 | 2. Provide all necessary permissions like Accessibility service, Notification, Draw over other apps etc
28 | 3. On Android 13+ devices, you need to additionally allow restricted settings before enabling the accessibility permission. Watch a tutorial here -> https://youtu.be/91B72lEpcqc?si=PCKKUSwM1aLdELqJ
29 | 4. It is highly recommended to download the app directly from f-droid rather than a .apk from github or f-droid website
--------------------------------------------------------------------------------
/fastlane/metadata/android/en-US/images/featureGraphic.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/nethical6/digipaws/3e57d27af103a2421ec70d3f96da212acaaec47b/fastlane/metadata/android/en-US/images/featureGraphic.png
--------------------------------------------------------------------------------
/fastlane/metadata/android/en-US/images/icon.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/nethical6/digipaws/3e57d27af103a2421ec70d3f96da212acaaec47b/fastlane/metadata/android/en-US/images/icon.png
--------------------------------------------------------------------------------
/fastlane/metadata/android/en-US/images/phoneScreenshots/1.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/nethical6/digipaws/3e57d27af103a2421ec70d3f96da212acaaec47b/fastlane/metadata/android/en-US/images/phoneScreenshots/1.png
--------------------------------------------------------------------------------
/fastlane/metadata/android/en-US/images/phoneScreenshots/2.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/nethical6/digipaws/3e57d27af103a2421ec70d3f96da212acaaec47b/fastlane/metadata/android/en-US/images/phoneScreenshots/2.png
--------------------------------------------------------------------------------
/fastlane/metadata/android/en-US/images/phoneScreenshots/3.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/nethical6/digipaws/3e57d27af103a2421ec70d3f96da212acaaec47b/fastlane/metadata/android/en-US/images/phoneScreenshots/3.png
--------------------------------------------------------------------------------
/fastlane/metadata/android/en-US/images/phoneScreenshots/4.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/nethical6/digipaws/3e57d27af103a2421ec70d3f96da212acaaec47b/fastlane/metadata/android/en-US/images/phoneScreenshots/4.png
--------------------------------------------------------------------------------
/fastlane/metadata/android/en-US/images/phoneScreenshots/5.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/nethical6/digipaws/3e57d27af103a2421ec70d3f96da212acaaec47b/fastlane/metadata/android/en-US/images/phoneScreenshots/5.png
--------------------------------------------------------------------------------
/fastlane/metadata/android/en-US/images/phoneScreenshots/6.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/nethical6/digipaws/3e57d27af103a2421ec70d3f96da212acaaec47b/fastlane/metadata/android/en-US/images/phoneScreenshots/6.png
--------------------------------------------------------------------------------
/fastlane/metadata/android/en-US/images/phoneScreenshots/7.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/nethical6/digipaws/3e57d27af103a2421ec70d3f96da212acaaec47b/fastlane/metadata/android/en-US/images/phoneScreenshots/7.png
--------------------------------------------------------------------------------
/fastlane/metadata/android/en-US/images/phoneScreenshots/8.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/nethical6/digipaws/3e57d27af103a2421ec70d3f96da212acaaec47b/fastlane/metadata/android/en-US/images/phoneScreenshots/8.png
--------------------------------------------------------------------------------
/fastlane/metadata/android/en-US/short_description.txt:
--------------------------------------------------------------------------------
1 | Monitor and Cut Down Your Screen Time Effectively
--------------------------------------------------------------------------------
/fastlane/metadata/android/en-US/title.txt:
--------------------------------------------------------------------------------
1 | DigiPaws - Digital Wellbeing and App Blocker
--------------------------------------------------------------------------------
/gradle.properties:
--------------------------------------------------------------------------------
1 | # Project-wide Gradle settings.
2 | # IDE (e.g. Android Studio) users:
3 | # Gradle settings configured through the IDE *will override*
4 | # any settings specified in this file.
5 | # For more details on how to configure your build environment visit
6 | # http://www.gradle.org/docs/current/userguide/build_environment.html
7 | # Specifies the JVM arguments used for the daemon process.
8 | # The setting is particularly useful for tweaking memory settings.
9 | org.gradle.jvmargs=-Xmx2048m -Dfile.encoding=UTF-8
10 | # When configured, Gradle will run in incubating parallel mode.
11 | # This option should only be used with decoupled projects. For more details, visit
12 | # https://developer.android.com/r/tools/gradle-multi-project-decoupled-projects
13 | # org.gradle.parallel=true
14 | # AndroidX package structure to make it clearer which packages are bundled with the
15 | # Android operating system, and which are packaged with your app's APK
16 | # https://developer.android.com/topic/libraries/support-library/androidx-rn
17 | android.useAndroidX=true
18 | # Kotlin code style for this project: "official" or "obsolete":
19 | kotlin.code.style=official
20 | # Enables namespacing of each library's R class so that its R class includes only the
21 | # resources declared in the library itself and none from the library's dependencies,
22 | # thereby reducing the size of the R class for that library
23 | android.nonTransitiveRClass=true
--------------------------------------------------------------------------------
/gradle/libs.versions.toml:
--------------------------------------------------------------------------------
1 | [versions]
2 | agp = "8.7.2"
3 | anychartAndroid = "1.1.5"
4 | api = "13.1.5"
5 | gson = "2.11.0"
6 | kotlin = "1.9.24"
7 | coreKtx = "1.13.1"
8 | junit = "4.13.2"
9 | junitVersion = "1.2.1"
10 | espressoCore = "3.6.1"
11 | appcompat = "1.7.0"
12 | material = "1.12.0"
13 | activity = "1.9.2"
14 | constraintlayout = "2.1.4"
15 | mpandroidchart = "v3.1.0"
16 | timerangepicker = "1.0.0"
17 |
18 | [libraries]
19 | androidx-core-ktx = { group = "androidx.core", name = "core-ktx", version.ref = "coreKtx" }
20 | anychart-android = { module = "com.github.AnyChart:AnyChart-Android", version.ref = "anychartAndroid" }
21 | api = { module = "dev.rikka.shizuku:api", version.ref = "api" }
22 | gson = { module = "com.google.code.gson:gson", version.ref = "gson" }
23 | junit = { group = "junit", name = "junit", version.ref = "junit" }
24 | androidx-junit = { group = "androidx.test.ext", name = "junit", version.ref = "junitVersion" }
25 | androidx-espresso-core = { group = "androidx.test.espresso", name = "espresso-core", version.ref = "espressoCore" }
26 | androidx-appcompat = { group = "androidx.appcompat", name = "appcompat", version.ref = "appcompat" }
27 | material = { group = "com.google.android.material", name = "material", version.ref = "material" }
28 | androidx-activity = { group = "androidx.activity", name = "activity", version.ref = "activity" }
29 | androidx-constraintlayout = { group = "androidx.constraintlayout", name = "constraintlayout", version.ref = "constraintlayout" }
30 | mpandroidchart = { module = "com.github.PhilJay:MPAndroidChart", version.ref = "mpandroidchart" }
31 | provider = { module = "dev.rikka.shizuku:provider", version.ref = "api" }
32 | timerangepicker = { module = "nl.joery.timerangepicker:timerangepicker", version.ref = "timerangepicker" }
33 |
34 | [plugins]
35 | android-application = { id = "com.android.application", version.ref = "agp" }
36 | kotlin-android = { id = "org.jetbrains.kotlin.android", version.ref = "kotlin" }
37 |
38 |
--------------------------------------------------------------------------------
/gradle/wrapper/gradle-wrapper.jar:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/nethical6/digipaws/3e57d27af103a2421ec70d3f96da212acaaec47b/gradle/wrapper/gradle-wrapper.jar
--------------------------------------------------------------------------------
/gradle/wrapper/gradle-wrapper.properties:
--------------------------------------------------------------------------------
1 | #Thu Sep 26 17:03:27 IST 2024
2 | distributionBase=GRADLE_USER_HOME
3 | distributionPath=wrapper/dists
4 | distributionUrl=https\://services.gradle.org/distributions/gradle-8.10.2-bin.zip
5 | zipStoreBase=GRADLE_USER_HOME
6 | zipStorePath=wrapper/dists
7 |
--------------------------------------------------------------------------------
/gradlew.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 |
--------------------------------------------------------------------------------
/local:
--------------------------------------------------------------------------------
1 | ## This file is automatically generated by Android Studio.
2 | # Do not modify this file -- YOUR CHANGES WILL BE ERASED!
3 | #
4 | # This file should *NOT* be checked into Version Control Systems,
5 | # as it contains information specific to your local configuration.
6 | #
7 | # Location of the SDK. This is only used by Gradle.
8 | # For customization when using a Version Control System, please read the
9 | # header note.
10 | sdk.dir=C\:\\Users\\agupt\\AppData\\Local\\Android\\Sdk
--------------------------------------------------------------------------------
/settings.gradle.kts:
--------------------------------------------------------------------------------
1 | pluginManagement {
2 | repositories {
3 | google {
4 | content {
5 | includeGroupByRegex("com\\.android.*")
6 | includeGroupByRegex("com\\.google.*")
7 | includeGroupByRegex("androidx.*")
8 | }
9 | }
10 | mavenCentral()
11 | gradlePluginPortal()
12 | }
13 | }
14 | dependencyResolutionManagement {
15 | repositoriesMode.set(RepositoriesMode.FAIL_ON_PROJECT_REPOS)
16 | repositories {
17 | google()
18 | mavenCentral()
19 | maven {
20 | setUrl("https://jitpack.io")
21 | }
22 | }
23 | }
24 |
25 | rootProject.name = "DigiPaws"
26 | include(":app")
27 |
--------------------------------------------------------------------------------