From 0d0a3f3883beb8b899b56074ddd83e6d68ad9d81 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Alexander=20D=C3=B6rflinger?= Date: Tue, 8 Apr 2025 12:36:59 +0200 Subject: [PATCH] [Flashlight] Added On-Screen Flashlight support Added support for an on-screen flashlight, which is basically a fragment showing a pre-defined color on a full screen canvas with the option to adjust the brightness of the screen. --- app/src/main/AndroidManifest.xml | 2 + .../familyhelpers/FlashlightActivity.java | 54 ++++ .../utils/DisplayBrightnessController.java | 277 ++++++++++++++++++ .../main/res/layout/activity_flashlight.xml | 135 +++++++++ app/src/main/res/values-de/strings.xml | 21 ++ app/src/main/res/values-en/strings.xml | 21 ++ app/src/main/res/values/colors.xml | 10 +- app/src/main/res/values/dimens.xml | 15 + app/src/main/res/values/strings.xml | 32 +- app/src/main/res/xml/root_preferences.xml | 24 ++ 10 files changed, 582 insertions(+), 9 deletions(-) create mode 100644 app/src/main/java/com/aldo/apps/familyhelpers/utils/DisplayBrightnessController.java diff --git a/app/src/main/AndroidManifest.xml b/app/src/main/AndroidManifest.xml index 7745cf2..efae3d4 100644 --- a/app/src/main/AndroidManifest.xml +++ b/app/src/main/AndroidManifest.xml @@ -72,6 +72,8 @@ android:exported="true"> + + diff --git a/app/src/main/java/com/aldo/apps/familyhelpers/FlashlightActivity.java b/app/src/main/java/com/aldo/apps/familyhelpers/FlashlightActivity.java index ea9eea9..6b10fef 100644 --- a/app/src/main/java/com/aldo/apps/familyhelpers/FlashlightActivity.java +++ b/app/src/main/java/com/aldo/apps/familyhelpers/FlashlightActivity.java @@ -1,7 +1,13 @@ package com.aldo.apps.familyhelpers; +import androidx.annotation.ColorRes; +import androidx.annotation.IdRes; +import androidx.annotation.LayoutRes; import androidx.annotation.RequiresApi; import androidx.appcompat.app.AppCompatActivity; +import androidx.core.view.WindowCompat; +import androidx.core.view.WindowInsetsCompat; +import androidx.core.view.WindowInsetsControllerCompat; import android.graphics.PorterDuff; import android.os.Build; @@ -11,6 +17,7 @@ import android.view.View; import android.widget.ImageView; import com.aldo.apps.familyhelpers.ui.FlashLightLevelShifter; +import com.aldo.apps.familyhelpers.utils.DisplayBrightnessController; import com.aldo.apps.familyhelpers.utils.FlashlightHelper; import com.google.android.material.button.MaterialButton; @@ -40,13 +47,34 @@ public class FlashlightActivity extends AppCompatActivity { */ private FlashlightHelper mFlashlightHelper; + /** + * The fullscreen view that acts as the on-screen flashlight. + */ + private View mOnScreenFlashlight; + + /** + * The {@link WindowInsetsControllerCompat} to control fullscreen mode. + */ + private WindowInsetsControllerCompat mFullscreenController; + + private DisplayBrightnessController mBrightnessController; + @RequiresApi(api = Build.VERSION_CODES.TIRAMISU) @Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.activity_flashlight); + mFullscreenController = WindowCompat.getInsetsController(getWindow(), + getWindow().getDecorView()); + mBrightnessController = new DisplayBrightnessController(findViewById(R.id.tv_current_brightness), this); initButtons(); + mOnScreenFlashlight = findViewById(R.id.on_screen_flashlight_view); + mOnScreenFlashlight.setOnClickListener(v -> { + mOnScreenFlashlight.setVisibility(View.GONE); + mFullscreenController.hide(WindowInsetsCompat.Type.systemBars()); + }); + mBrightnessController.setupSwipeGesture(mOnScreenFlashlight); mLightOnIndicator = findViewById(R.id.flashlight_status_indicator); mFlashlightLevelShifter = findViewById(R.id.flashlight_level_shifter); mFlashlightHelper = new FlashlightHelper(this); @@ -81,6 +109,13 @@ public class FlashlightActivity extends AppCompatActivity { mFlashlightLevelShifter.setProgress(maxBrightness); mFlashlightHelper.setFlashlightBrightness(maxBrightness); }); + initializeColorButton(R.id.btn_onscreen_flashlight_red, R.color.flashlight_onscreen_red, false); + initializeColorButton(R.id.btn_onscreen_flashlight_orange, R.color.flashlight_onscreen_orange, false); + initializeColorButton(R.id.btn_onscreen_flashlight_blue, R.color.flashlight_onscreen_blue, false); + initializeColorButton(R.id.btn_onscreen_flashlight_green, R.color.flashlight_onscreen_green, false); + initializeColorButton(R.id.btn_onscreen_flashlight_gray, R.color.flashlight_onscreen_gray, false); + initializeColorButton(R.id.btn_onscreen_flashlight_white, R.color.flashlight_onscreen_white, true); + initializeColorButton(R.id.btn_onscreen_flashlight_yellow, R.color.flashlight_onscreen_yellow, true); } @Override @@ -106,6 +141,25 @@ public class FlashlightActivity extends AppCompatActivity { } } + /** + * Helper method to initialize a color button by applying the proper color to the surface and + * the textview. + * + * @param btnId The ID of the button to initialize. + * @param colorId The id of the color top apply. + * @param isLight True if it is a light color, false otherwise. + */ + private void initializeColorButton(@IdRes final int btnId, + @ColorRes final int colorId, + final boolean isLight) { + findViewById(btnId).setOnClickListener(v -> { + mOnScreenFlashlight.setBackgroundColor(getColor(colorId)); + mOnScreenFlashlight.setVisibility(View.VISIBLE); + mFullscreenController.hide(WindowInsetsCompat.Type.systemBars()); + mBrightnessController.setLightModeColor(isLight); + }); + } + /** * Helper method to print any potential errors while subscribing to the current status observable. * diff --git a/app/src/main/java/com/aldo/apps/familyhelpers/utils/DisplayBrightnessController.java b/app/src/main/java/com/aldo/apps/familyhelpers/utils/DisplayBrightnessController.java new file mode 100644 index 0000000..3c76287 --- /dev/null +++ b/app/src/main/java/com/aldo/apps/familyhelpers/utils/DisplayBrightnessController.java @@ -0,0 +1,277 @@ +package com.aldo.apps.familyhelpers.utils; + +import android.app.Activity; +import android.content.Context; +import android.content.SharedPreferences; +import android.util.Log; +import android.view.GestureDetector; +import android.view.MotionEvent; +import android.view.View; +import android.view.WindowManager; +import android.widget.TextView; + +import androidx.annotation.ColorRes; +import androidx.annotation.IntegerRes; +import androidx.annotation.NonNull; +import androidx.annotation.StringRes; +import androidx.preference.PreferenceManager; + +import com.aldo.apps.familyhelpers.R; + +import java.lang.ref.WeakReference; + +/** + * Utility class to control the display brightness of an Android activity using swipe gestures. + *

+ * This class allows users to adjust the screen brightness by swiping up or down on a specified view. + * It also displays the current brightness level in a TextView. The brightness adjustment + * is controlled by user preferences for sensitivity and step size. + */ +public class DisplayBrightnessController { + + /** + * Tag for debugging purposes. + */ + private static final String TAG = "DisplayBrightnessController"; + + /** + * Maximum allowed display brightness value. + */ + private static final int DISPLAY_BRIGHTNESS_MAX_VALUE = 255; + + /** + * Minimum allowed display brightness value. + */ + private static final int DISPLAY_BRIGHTNESS_MIN_VALUE = 1; + + /** + * Base value for swipe sensitivity calculation. + */ + private static final int SWIPE_SENSITIVITY_BASE = 10; + + /** + * TextView to display the current brightness level. + */ + private final TextView mCurrentBrightnessText; + + /** + * {@link WeakReference} to the {@link Context} of the application. + */ + private final WeakReference mContextRef; + + /** + * {@link WeakReference} to the {@link Activity} whose screen brightness is being controlled. + */ + private final WeakReference mActivityRef; + + /** + * SharedPreferences instance for accessing user preferences. + */ + private final SharedPreferences mPreferences; + + /** + * Current brightness level. + */ + private int mCurrentBrightness = 125; + + /** + * Initial Y coordinate of the touch event for swipe detection. + */ + private float mInitialY; + + /** + * Constructor for DisplayBrightnessController. + * + * @param textView The TextView used to display the current brightness level. + * @param activity The Activity whose screen brightness will be controlled. + * The Activity's application context is used for SharedPreferences. + */ + public DisplayBrightnessController(final TextView textView, final Activity activity) { + mCurrentBrightnessText = textView; + mActivityRef = new WeakReference<>(activity); + mContextRef = new WeakReference<>(activity.getApplicationContext()); + mPreferences = PreferenceManager.getDefaultSharedPreferences(activity.getApplicationContext()); + } + + /** + * Sets up a swipe gesture listener on the provided view to control screen brightness. + *

+ * The gesture listener detects vertical swipes. Swiping up increases brightness, and swiping + * down decreases it. The sensitivity of the swipe and the amount of brightness change + * per swipe are determined by user preferences. A single tap hides or shows the brightness text. + * + * @param view The view on which the swipe gesture should be detected. + */ + public void setupSwipeGesture(final View view) { + final Context context = mContextRef.get(); + if (context == null) { + Log.e(TAG, "setupSwipeGesture: Context null, cannot continue"); + return; + } + final GestureDetector gestureDetector = new GestureDetector(context, new GestureDetector.SimpleOnGestureListener() { + @Override + public boolean onDown(@NonNull final MotionEvent e) { + mInitialY = e.getY(); + return true; + } + + @Override + public boolean onScroll(final MotionEvent e1, + @NonNull final MotionEvent e2, + final float distanceX, + final float distanceY) { + final float diffY = e2.getY() - mInitialY; + mInitialY = e2.getY(); + + // Get sensitivity from preferences. Fall back to default if not found. + final float sensitivityValue = (float) SWIPE_SENSITIVITY_BASE / mPreferences.getInt( + getString(R.string.pref_key_on_screen_flashlight_sensitivity), + getInt(R.integer.pref_on_screen_flashlight_sensitivity_def)); + + Log.d(TAG, "onScroll: Started with Sensitivity = [" + sensitivityValue + "]"); + if (Math.abs(diffY) > sensitivityValue) { + Log.d(TAG, "onScroll: Relevant swipe detected"); + mCurrentBrightnessText.setVisibility(View.VISIBLE); + adjustBrightness(diffY < 0); + } + return true; + } + + @Override + public boolean onSingleTapUp(@NonNull final MotionEvent e) { + Log.d(TAG, "onSingleTapUp: Tap detected, set visibility to GONE"); + // Toggle visibility of the brightness text on single tap + if (View.VISIBLE == mCurrentBrightnessText.getVisibility()) { + mCurrentBrightnessText.setVisibility(View.GONE); + } else { + view.setVisibility(View.GONE); // added to hide view. + } + return true; // Indicate that this tap is handled + } + }); + + // Set the touch listener on the view to use the gesture detector + view.setOnTouchListener((v, event) -> gestureDetector.onTouchEvent(event)); + } + + /** + * Adjusts the screen brightness based on the swipe direction. + *

+ * This method reads the brightness step size from preferences and changes the brightness + * by that amount. It also updates the brightness TextView. + * + * @param increase True to increase brightness, false to decrease. + */ + private void adjustBrightness(final boolean increase) { + Log.d(TAG, "adjustBrightness() called with: increase = [" + increase + "]"); + // Get step size from preferences. Fall back to default if not found. + final int stepSize = mPreferences.getInt( + getString(R.string.pref_key_on_screen_flashlight_step_size), + getInt(R.integer.pref_on_screen_flashlight_step_size_def)); + + Log.d(TAG, "adjustBrightness: Called with StepSize = [" + stepSize + + "] and current brightness = [" + mCurrentBrightness + "]"); + // Check if current brightness is within valid bounds + if (DISPLAY_BRIGHTNESS_MIN_VALUE <= mCurrentBrightness + && mCurrentBrightness <= DISPLAY_BRIGHTNESS_MAX_VALUE) { + Log.d(TAG, "adjustBrightness: Called within valid range"); + final int newBrightness; + if (increase) { + newBrightness = Math.min(DISPLAY_BRIGHTNESS_MAX_VALUE, mCurrentBrightness + stepSize); + } else { + newBrightness = Math.max(DISPLAY_BRIGHTNESS_MIN_VALUE, mCurrentBrightness - stepSize); + } + setBrightness(newBrightness); + updateBrightnessText(newBrightness); + mCurrentBrightness = newBrightness; + } + } + + /** + * Sets the screen brightness of the activity. + * + * @param brightness The new brightness value (0-255). + */ + private void setBrightness(final int brightness) { + final Activity activity = mActivityRef.get(); + if (activity == null) { + Log.e(TAG, "setBrightness: Activity is null, cannot continue"); + return; + } + Log.d(TAG, "setBrightness: Setting the screen brightness to [" + brightness + "]"); + final WindowManager.LayoutParams layout = activity.getWindow().getAttributes(); + layout.screenBrightness = brightness / (float) DISPLAY_BRIGHTNESS_MAX_VALUE; + activity.getWindow().setAttributes(layout); + } + + /** + * Updates the brightness TextView with the current brightness value. + * + * @param brightnessValue The current brightness value. + */ + private void updateBrightnessText(final int brightnessValue) { + Log.d(TAG, "updateBrightnessText() called with: brightnessValue = [" + brightnessValue + "]"); + mCurrentBrightnessText.setText(String.format( + getString(R.string.flashlight_on_screen_brightness_base_text) ,brightnessValue)); + } + + /** + * Helper method to set the text color adjustable to either light of dark colors to be visible + * on either surface. + * + * @param isLightModeColor true if the color is a light color, false otherwise. + */ + public void setLightModeColor(final boolean isLightModeColor) { + Log.d(TAG, "setLightModeColor() called with: isLightModeColor = [" + isLightModeColor + "]"); + if (isLightModeColor) { + mCurrentBrightnessText.setTextColor(getColor(R.color.black)); + } else { + mCurrentBrightnessText.setTextColor(getColor(R.color.white)); + } + } + + /** + * Helper method to get a string resource from the activity. + * + * @param textId The resource ID of the string. + * @return The string value. + */ + private String getString(@StringRes final int textId) { + final Activity activity = mActivityRef.get(); + if (activity == null) { + Log.e(TAG, "getString: Activity is null, cannot continue"); + return ""; + } + return activity.getString(textId); + } + + /** + * Helper method to get a color resource from the activity. + * + * @param colorId The resource ID of the color. + * @return The color value. + */ + private int getColor(@ColorRes final int colorId) { + final Activity activity = mActivityRef.get(); + if (activity == null) { + Log.e(TAG, "getInt: Activity is null, cannot continue"); + return 0; + } + return activity.getColor(colorId); + } + + /** + * Helper method to get an integer resource from the activity. + * + * @param intId The resource ID of the integer. + * @return The integer value. + */ + private int getInt(@IntegerRes final int intId) { + final Activity activity = mActivityRef.get(); + if (activity == null) { + Log.e(TAG, "getInt: Activity is null, cannot continue"); + return 0; + } + return activity.getResources().getInteger(intId); + } +} diff --git a/app/src/main/res/layout/activity_flashlight.xml b/app/src/main/res/layout/activity_flashlight.xml index c50eb6b..0591abf 100644 --- a/app/src/main/res/layout/activity_flashlight.xml +++ b/app/src/main/res/layout/activity_flashlight.xml @@ -6,6 +6,115 @@ xmlns:app="http://schemas.android.com/apk/res-auto" tools:context=".FlashlightActivity"> + + +