diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md index d2d8b7852..f0143da96 100644 --- a/CONTRIBUTING.md +++ b/CONTRIBUTING.md @@ -1,102 +1,128 @@ # Contributing to Essentials -Thank you for your interest in contributing to Essentials! This guide will help you set up your development environment and understand the architecture of the project. +Thank you for your interest in contributing to Essentials! This guide details the development setup, architectural conventions, state management patterns, UI component reuse guidelines, service decoupling practices, and pull request workflows. + +--- ## Environment Setup -1. **Android Studio**: Download and install the latest version of [Android Studio](https://developer.android.com/studio). -2. **JDK**: Ensure you have JDK 17 or higher installed. -3. **Clone the project**: +1. **Android Studio**: Use the latest stable release of [Android Studio](https://developer.android.com/studio). +2. **JDK**: Use JDK 17 or higher. +3. **Clone Repository**: ```bash git clone https://github.com/sameerasw/essentials.git ``` -4. **Open in Android Studio**: Open the project and wait for Gradle to sync. -5. **Shizuku**: Many features require [Shizuku](https://shizuku.rikka.app/). Install it on your device for testing. +4. **Target Branch**: Ensure all branches and pull requests are based on and targeted to merge back into **`develop`**. +5. **Shizuku / Root**: Many privileged features require [Shizuku](https://shizuku.rikka.app/) or Root for testing on your device or emulator. + +--- + +## Core Architectural & Development Principles -## Architecture Overview +### 1. State Management & ViewModel Integration +- **Complete End-to-End Pipeline**: Ensure any new UI control or `FeatureRegistry.kt` entry is backed by a complete state flow: + - Provide typed getter and setter methods in [`SettingsRepository`](file:///Users/sameerasandakelum/GIT/essentials/app/src/main/java/com/sameerasw/essentials/data/repository/SettingsRepository.kt). + - Expose reactive state (`mutableStateOf`) and mutator functions in the corresponding ViewModel (e.g. `MainViewModel`, `NetworksViewModel`). + - Connect the UI composable and `FeatureRegistry.onToggle` directly to these ViewModel methods. +- **Centralized Preference Keys**: Define all preference keys and helper accessors inside `SettingsRepository` to keep keys uniform and discoverable. +- **Database & Persistent Properties**: Maintain clean migrations for persistent properties to preserve built-in configuration export and import integrity. -Essentials follows a modern Android architecture: +--- -- **Language**: Kotlin -- **UI Framework**: Jetpack Compose -- **Pattern**: MVVM (Model-View-ViewModel) -- **Dependency Injection**: Manual injection (view models are managed by `MainViewModel` or passed through activities). +### 2. Service Decoupling & Modularity +- **Preserve Shared Services**: Keep shared background services (such as `ScreenOffAccessibilityService`) lightweight and focused on their core responsibilities. +- **Use Dedicated Handlers & Controllers**: + - Encapsulate feature-specific listeners (e.g. connectivity changes, sensor observers, audio events) in dedicated controllers or handlers under `domain/controller/` or `services/handlers/`. + - Connect external events to shared services through clean adapters or listeners to maintain clear separation of concerns. -## Feature Implementation Workflow +--- -Adding a new feature involves three main steps: +### 3. Privileged Execution & Error Feedback +- **Transparent Execution**: When executing shell commands (`ShellUtils.runCommand`), system APIs, or Shizuku binders: + - Check command return codes and handle permission exceptions gracefully. + - Provide clear UI feedback (such as guidance sheets, permission cards, or status indicators) if privileged execution cannot be completed. +- **Pre-Flight Permission Checks**: Verify required permissions (`WRITE_SECURE_SETTINGS`, Shizuku, Root, Accessibility) before initiating restricted actions. -### 1. Define Metadata in `FeatureRegistry.kt` +--- -All features must be registered in the `FeatureRegistry` object. This centralizes metadata and enables automated search indexing. +### 4. Component Reuse & Design System +Leverage the rich design system components in `app/src/main/java/com/sameerasw/essentials/ui/core/` to maintain a consistent Material 3 Expressive interface: +- **Containers (`ui/core/containers/`)**: + - `RoundedCardContainer`: Use for grouping related settings items. + - `RoundedCardLazyContainer`: Use for scrolling list containers. +- **Cards & Settings Items (`ui/core/cards/`)**: + - `IconToggleItem`: Use for standard toggle rows with an icon, title, description, and switch. Always supply `index` and `count` for seamless shape morphing. + - `ConfigPickerItem`: Use for settings rows opening picker dialogs or bottom sheets. + - `FeatureCard`: Use for highlighted feature banners using pastel background palettes (`ColorUtil.getPastelColorFor`) and vibrant icons (`ColorUtil.getVibrantColorFor`). + - `PermissionCard`: Use for consistent permission status displays and action triggers. +- **Pickers (`ui/core/pickers/`)**: + - `SegmentedPicker`, `MultiSegmentedPicker`: Use for connected button groups with built-in tactile feedback. +- **Bottom Sheets (`ui/core/sheets/`)**: + - `EssentialsBottomSheet`, `PermissionsBottomSheet`, `FeatureHelpBottomSheet`: Use for modal sheets and feature guidance. -```kotlin -object : Feature( - id = "MyNewFeature", - title = "My New Feature", - iconRes = R.drawable.my_feature_icon, - category = "Tools", - description = "A short description of the feature", - permissionKeys = listOf("ACCESSIBILITY"), // Optional - searchableSettings = listOf( - SearchSetting("Option Title", "Description", "highlight_key", listOf("keyword1", "keyword2")) - ) -) { - override fun isEnabled(viewModel: MainViewModel) = viewModel.isMyFeatureEnabled.value - override fun onToggle(viewModel: MainViewModel, context: Context, enabled: Boolean) { - viewModel.setMyFeatureEnabled(enabled, context) - } -} -``` +> [!TIP] +> Always check `ui/core/` before creating custom cards, list items, or containers to ensure visual harmony and maintainability. -### 2. Create the Settings UI +--- -Create a new composable in `app/src/main/java/com/sameerasw/essentials/ui/composables/configs/`. +### 5. Jetpack Compose & Material 3 Expressive Conventions +- **Top-Level Package Imports**: Place all class and symbol imports at the top of the file and reference items by their simple names. +- **Structured Grouping**: Group related settings into `RoundedCardContainer` blocks to maintain visual hierarchy. +- **Material 3 Expressive Theming**: + - Use `surfaceContainer` for outer card containers and `surfaceContainerHigh` for modal bottom sheets. + - Ensure compatibility with Dynamic Color and Pitch Black (pure `#000000` AMOLED) token palettes. +- **Supportive Disabled States**: When a feature is inactive or missing requirements, use `enabled = false` paired with `onDisabledClick` to present an explanatory guidance sheet. -- Use `RoundedCardContainer` for grouped items. -- Use `IconToggleItem` or `SimpleToggleItem` for toggles. -- Use `Modifier.highlight(highlightSetting == "key")` to support search highlighting. +--- -### 3. Register in `FeatureSettingsActivity.kt` +### 6. Iconography & String Localization +- **String Resources**: Place all user-visible text in `app/src/main/res/values/strings.xml` and access them via `stringResource(R.string...)` or `context.getString(R.string...)`. Check existing entries first to avoid duplicates. +- **Drawable Resources**: Use rounded drawable resources (`R.drawable.rounded_*`) across UI elements and Quick Settings tiles. -Add your new UI to the `when(feature)` block in `FeatureSettingsActivity.kt` to link it to the registration ID. +--- -```kotlin -"MyNewFeature" -> { - MyNewFeatureSettingsUI( - viewModel = viewModel, - modifier = Modifier.padding(top = 16.dp), - highlightSetting = highlightSetting - ) -} -``` +### 7. Tactile Haptic Feedback (`HapticUtil`) +- **Interactive UI Feedback**: Integrate appropriate haptic responses on buttons, switches, sliders, segment pickers, and tiles using `HapticUtil` (`performUIHaptic`, `performVirtualKeyHaptic`, `performHeavyHaptic`, `performLightHaptic`). +- **Background & Tile Actions**: Use `HapticUtil.performHapticForService(context)` inside background services and QS tile interactions. +- **Feature Haptic Preferences**: Respect user-configured haptic profiles when available. -## Search System +--- -The search system is fully automated. By adding `SearchSetting` objects to your feature in `FeatureRegistry.kt`, they will automatically: +### 8. Quick Settings Tile Integration +- Follow the step-by-step developer guide in [ADD_QS_TILE.md](file:///Users/sameerasandakelum/GIT/essentials/docs/ADD_QS_TILE.md) when adding new tiles. +- Declare the service in `AndroidManifest.xml`, register in `QsTileRegistry.kt`, support headless execution in `QsTileActionRouter.kt`, list in `QuickSettingsTilesSettingsUI.kt`, and test on the **Favorite QS Tiles Glance Widget**. -1. Appear in the universal search results. -2. Navigate the user to the correct feature screen. -3. Trigger a pulse animation on the target item via the `highlight` modifier. +--- -## Code Style +### 9. Universal Search Integration (`FeatureRegistry.kt`) +- Register configurable settings in `FeatureRegistry.kt` using `SearchSetting(...)` entries. +- Attach `Modifier.highlight(highlightSetting == "key")` to composables so universal search can smoothly navigate to and highlight target items. -- Use **PascalCase** for Composables. -- Use **camelCase** for variables and functions. -- Prefer **functional components** and avoid heavy logic in the UI layer. +--- -## Pull Requests +### 10. Code Style & Technical Documentation +- Write clean, concise, and idiomatic Kotlin. +- Use clear, technical comments where complex architecture, system settings, or low-level hardware interactions benefit from explanation. -We welcome pull requests! To ensure a smooth review process, please follow these guidelines: +--- -1. **Create a Branch**: Create a new branch for your feature or bugfix (e.g., `feature/my-new-feature` or `fix/issue-description`). -2. **Keep it Focused**: A PR should ideally do one thing. If you have multiple unrelated changes, please separate them into multiple PRs. -3. **Test Your Changes**: Before submitting, ensure that your changes build correctly and that you've tested them on a physical device or emulator. -4. **Describe Your Work**: In your PR description, explain _what_ you changed and _why_. If your change affects the UI, please include screenshots or a screen recording. -5. **Code Style**: Ensure your code follows the existing style of the project. -6. **Update Documentation**: If you've added a new feature, ensure you've registered it in `FeatureRegistry.kt` as described above so it's searchable. -7. **All to develop**: Please make sure your branches are based on `develop` and also they are set to merge back to `develop` as well. +## Best Practices Reference -## Questions? +| Area | Recommended Pattern | Context | +| :--- | :--- | :--- | +| **ViewModel State** | Expose reactive states via `SettingsRepository` and ViewModel methods | Ensures clean compilation, predictable state flow, and working search toggles. | +| **Background Logic** | Encapsulate features in modular handlers under `domain/controller/` | Keeps shared services (e.g. accessibility service) clean and isolated. | +| **Import Hygiene** | Place all package imports at the top of the file | Keeps code readable and conforms to project styling conventions. | +| **Preferences** | Store and access keys through constants in `SettingsRepository` | Prevents typos and centralizes data contracts. | +| **Privilege Feedback** | Validate permissions and provide clear UI feedback on failures | Keeps users informed when elevated permissions are needed. | +| **UI Components** | Use `RoundedCardContainer`, `IconToggleItem`, and `ui/core/` composables | Preserves design consistency and built-in shape morphing across all screens. | -If you have any questions or need help, feel free to open an issue or reach out in our community channels. +--- + +## Pull Request Workflow + +1. **Branching**: Create a feature or fix branch from `develop` (e.g. `feature/my-feature` or `fix/issue-description`). +2. **Target**: Point all pull requests to the `develop` branch. +3. **Focused Scope**: Keep changes cohesive and centered around a single feature or bug fix. +4. **Local Verification**: Verify the build compiles smoothly (`./gradlew assembleDebug`) and test functionality on a physical device or emulator. +5. **PR Description**: Include a clear summary of changes, rationale, and screenshots/recordings for any UI updates. diff --git a/docs/ADD_QS_TILE.md b/docs/ADD_QS_TILE.md new file mode 100644 index 000000000..6ef143d1b --- /dev/null +++ b/docs/ADD_QS_TILE.md @@ -0,0 +1,204 @@ +# Quick Settings (QS) Tile Implementation Guide + +This guide details the end-to-end process of implementing, registering, and maintaining Quick Settings (QS) tiles within the **Essentials** architecture. + +For system architecture context, refer to [ARCHITECTURE.md](file:///Users/sameerasandakelum/GIT/essentials/docs/ARCHITECTURE.md), [STRUCTURE.md](file:///Users/sameerasandakelum/GIT/essentials/docs/STRUCTURE.md), and [SERVICES_AND_PERMISSIONS.md](file:///Users/sameerasandakelum/GIT/essentials/docs/SERVICES_AND_PERMISSIONS.md). + +--- + +## Architectural Overview + +Quick Settings tile integration operates across three core layers: + +``` +┌─────────────────────────────────────────────────────────────┐ +│ System & Shade │ +│ Android Quick Settings Shade (SystemUI) │ +└──────────────────────────────┬──────────────────────────────┘ + │ + ▼ +┌─────────────────────────────────────────────────────────────┐ +│ BaseTileService Layer │ +│ - Standardized lifecycle & background coroutine scope │ +│ - Secure / Global settings cache & fallback bridge │ +│ - Permission & device support validation │ +│ - Built-in haptic feedback │ +└──────────────────────────────┬──────────────────────────────┘ + │ + ┌─────────────────────┴─────────────────────┐ + ▼ ▼ +┌─────────────────────────────────┐ ┌──────────────────────────────┐ +│ Discovery & Headless Execution │ │ In-App Tile Manager UI │ +│ - QsTileRegistry │ │ - QuickSettingsTilesSettingsUI +│ - QsTileActionRouter │ │ - StatusBarManager tile add │ +│ - QsTilesWidget (Glance) │ │ - PermissionsBottomSheet │ +└─────────────────────────────────┘ └──────────────────────────────┘ +``` + +--- + +## Step-by-Step Implementation Workflow + +### 1. Create Tile Service Class + +All QS tile services are located in `app/src/main/java/com/sameerasw/essentials/services/tiles/` and must extend [`BaseTileService`](file:///Users/sameerasandakelum/GIT/essentials/app/src/main/java/com/sameerasw/essentials/services/tiles/BaseTileService.kt). + +#### Implementation Example: + +```kotlin +package com.sameerasw.essentials.services.tiles + +import android.Manifest +import android.content.pm.PackageManager +import android.graphics.drawable.Icon +import android.service.quicksettings.Tile +import com.sameerasw.essentials.R +import com.sameerasw.essentials.utils.DeviceUtils + +class FeatureTileService : BaseTileService() { + + override fun getTileLabel(): String = getString(R.string.tile_feature_label) + + override fun getTileSubtitle(): String { + return if (isFeatureActive()) getString(R.string.on) else getString(R.string.off) + } + + override fun hasFeaturePermission(): Boolean { + return checkCallingOrSelfPermission(Manifest.permission.WRITE_SECURE_SETTINGS) == PackageManager.PERMISSION_GRANTED + } + + override fun isDeviceSupported(): Boolean { + // Optional override: Return false if feature is restricted to specific hardware/OEMs + return true + } + + override fun getTileIcon(): Icon? { + val iconRes = if (isFeatureActive()) { + R.drawable.rounded_feature_active_24 + } else { + R.drawable.rounded_feature_inactive_24 + } + return Icon.createWithResource(this, iconRes) + } + + override fun getTileState(): Int { + return if (isFeatureActive()) Tile.STATE_ACTIVE else Tile.STATE_INACTIVE + } + + override fun onTileClick() { + // Asynchronous execution within service coroutine scope + val newState = if (isFeatureActive()) 0 else 1 + putSecureInt("secure_feature_setting_key", newState) + } + + private fun isFeatureActive(): Boolean { + return getSecureInt("secure_feature_setting_key", 0) == 1 + } +} +``` + +#### `BaseTileService` Methods Reference: + +| Method | Return Type | Description | +| :--- | :--- | :--- | +| `onTileClick()` | `Unit` | Executed asynchronously when the tile is tapped. | +| `getTileLabel()` | `String` | Primary title string rendered on the tile. | +| `getTileSubtitle()` | `String` | Secondary status text (e.g. "On", "Off", timer remaining). | +| `getTileState()` | `Int` | `Tile.STATE_ACTIVE` or `Tile.STATE_INACTIVE`. | +| `hasFeaturePermission()` | `Boolean` | Permission validation. Returns `Tile.STATE_UNAVAILABLE` when `false`. | +| `isDeviceSupported()` | `Boolean` | Compatibility check. Controlled by "Enable unsupported features" setting. | +| `getTileIcon()` | `Icon?` | Optional dynamic icon resolution based on feature state. | +| `getSecureInt()` / `putSecureInt()` | `Int` / `Unit` | Read/write secure system settings with cached fallback to Shizuku/root shell. | +| `getGlobalInt()` / `putGlobalInt()` | `Int` / `Unit` | Read/write global system settings with cached fallback to Shizuku/root shell. | + +--- + +### 2. Android Manifest Registration + +Declare the service inside `` in [`AndroidManifest.xml`](file:///Users/sameerasandakelum/GIT/essentials/app/src/main/AndroidManifest.xml) with `BIND_QUICK_SETTINGS_TILE` permission: + +```xml + + + + + + +``` + +--- + +### 3. String Localization + +Declare user-facing tile labels and documentation strings in [`strings.xml`](file:///Users/sameerasandakelum/GIT/essentials/app/src/main/res/values/strings.xml): + +```xml + +Feature Name +Enables or disables Feature directly from your Quick Settings shade or widget. +``` + +--- + +### 4. Tile Registry Registration (`QsTileRegistry.kt`) + +Register the tile in [`QsTileRegistry.ALL_TILES`](file:///Users/sameerasandakelum/GIT/essentials/app/src/main/java/com/sameerasw/essentials/services/tiles/QsTileRegistry.kt): + +```kotlin +QsTileEntry( + titleRes = R.string.tile_feature_label, + iconRes = R.drawable.rounded_feature_24, + serviceClass = FeatureTileService::class.java +), +``` + +#### Glance Widget Integration: +- `QsTileRegistry` provides state resolution, label translation, dynamic icon rendering, and active status for the **Favorite QS Tiles Glance Widget** ([`QsTilesWidget.kt`](file:///Users/sameerasandakelum/GIT/essentials/app/src/main/java/com/sameerasw/essentials/services/widgets/QsTilesWidget.kt)). +- Standard `BaseTileService` subclasses are automatically queried via reflection (`isTileActive`, `getTileSubtitle`, `getTileIcon`). +- If state queries require an external controller (e.g. `CaffeinateController`), add a custom condition in `isTileActive()` / `getTileSubtitle()`. + +--- + +### 5. Headless Action Routing (`QsTileActionRouter.kt`) + +Tapping a tile inside the **Favorite QS Tiles Glance Widget** triggers [`QsTileClickActionCallback`](file:///Users/sameerasandakelum/GIT/essentials/app/src/main/java/com/sameerasw/essentials/services/widgets/QsTileClickActionCallback.kt), which dispatches to [`QsTileActionRouter`](file:///Users/sameerasandakelum/GIT/essentials/app/src/main/java/com/sameerasw/essentials/services/receivers/QsTileActionRouter.kt): + +- Standard `BaseTileService` subclasses are automatically initialized headlessly by `QsTileActionRouter` to invoke `onTileClick()`. +- If the feature requires broadcast routing or dedicated service intents, define an explicit dispatch branch in `QsTileActionRouter.kt`. + +--- + +### 6. In-App Tile Manager UI (`QuickSettingsTilesSettingsUI.kt`) + +All QS tiles must be added to the in-app Quick Settings Tiles settings screen so users can view permissions and add tiles directly to their system QS panel via `StatusBarManager.requestAddTileService()`. + +In [`QuickSettingsTilesSettingsUI.kt`](file:///Users/sameerasandakelum/GIT/essentials/app/src/main/java/com/sameerasw/essentials/ui/features/tiles/QuickSettingsTilesSettingsUI.kt), register the tile in `allTiles`: + +```kotlin +QSTileInfo( + titleRes = R.string.tile_feature_label, + iconRes = R.drawable.rounded_feature_24, + serviceClass = FeatureTileService::class.java, + permissionKeys = listOf("WRITE_SECURE_SETTINGS"), + aboutDescription = R.string.about_desc_feature_tile, + categoryRes = R.string.cat_utils // e.g. R.string.cat_visuals, R.string.cat_privacy, R.string.cat_accessibility +) +``` + +--- + +## Contributor Checklist + +- [ ] **Import Standards**: All package imports declared at the top of the file (no inline package references). +- [ ] **Localization**: User-facing strings added to `strings.xml` with no duplicates. +- [ ] **Iconography**: Rounded drawable resources (`R.drawable.rounded_*`) used. +- [ ] **Permission Handling**: Required permissions correctly mapped in `QSTileInfo` and validated in `hasFeaturePermission()`. +- [ ] **Glance Widget Support**: Tile verified in `QsTilesWidget` (state toggling, label/subtitle display, haptics). +- [ ] **In-App Management**: Tile visible under correct category in `QuickSettingsTilesSettingsUI` with functional "Add" button and "About" dialog. diff --git a/docs/SERVICES_AND_PERMISSIONS.md b/docs/SERVICES_AND_PERMISSIONS.md index e98f6197e..8b0f63d6c 100644 --- a/docs/SERVICES_AND_PERMISSIONS.md +++ b/docs/SERVICES_AND_PERMISSIONS.md @@ -23,3 +23,10 @@ This document outlines background services, Quick Settings tiles, and system per - **`WRITE_SECURE_SETTINGS`**: Granted via ADB (`adb shell pm grant com.sameerasw.essentials android.permission.WRITE_SECURE_SETTINGS`). Allows modifying system secure settings. - **`Shizuku` Binder Interface**: Enables executing privileged system API calls without full root access. - **`Root` (`su`)**: Used for direct kernel sysfs writes (e.g. charging current control, SurfaceFlinger adjustments). + +--- + +## Developer Guide + +For instructions on adding and registering new Quick Settings tiles, refer to [ADD_QS_TILE.md](file:///Users/sameerasandakelum/GIT/essentials/docs/ADD_QS_TILE.md). +