From 8126286609d11c37c1ea53d037e67c75fb7a874e Mon Sep 17 00:00:00 2001 From: Duarte Barbosa Date: Wed, 5 Feb 2025 00:48:10 +0000 Subject: [PATCH 1/3] New Timer Overlay that shows up real count while watching Fixed the counter getting count twice Some code cleanup / refactor --- .../com/scrolless/app/di/ProviderModule.kt | 17 ++ .../app/features/home/HomeFragment.kt | 21 +- .../app/overlay/TimerOverlayManager.kt | 27 +++ .../app/overlay/TimerOverlayManagerImpl.kt | 192 ++++++++++++++++++ .../com/scrolless/app/provider/AppProvider.kt | 4 + .../scrolless/app/provider/AppProviderImpl.kt | 17 ++ .../app/provider/UsageTrackerImpl.kt | 1 + .../scrolless/app/services/BlockController.kt | 74 +++---- .../app/services/BlockControllerImpl.kt | 93 +++++++++ .../ScrollessBlockAccessibilityService.kt | 49 +++-- .../services/handlers/DayLimitBlockHandler.kt | 4 +- app/src/main/res/layout/fragment_home.xml | 14 +- app/src/main/res/layout/overlay_timer.xml | 38 ++++ app/src/main/res/values/dimens.xml | 1 + app/src/main/res/values/strings.xml | 1 + .../framework/extensions/LongExtension.kt | 22 ++ .../framework/extensions/ViewExtension.kt | 67 ++++++ 17 files changed, 573 insertions(+), 69 deletions(-) create mode 100644 app/src/main/java/com/scrolless/app/overlay/TimerOverlayManager.kt create mode 100644 app/src/main/java/com/scrolless/app/overlay/TimerOverlayManagerImpl.kt create mode 100644 app/src/main/java/com/scrolless/app/services/BlockControllerImpl.kt create mode 100644 app/src/main/res/layout/overlay_timer.xml diff --git a/app/src/main/java/com/scrolless/app/di/ProviderModule.kt b/app/src/main/java/com/scrolless/app/di/ProviderModule.kt index bd4d1ef..79b08dc 100644 --- a/app/src/main/java/com/scrolless/app/di/ProviderModule.kt +++ b/app/src/main/java/com/scrolless/app/di/ProviderModule.kt @@ -5,6 +5,8 @@ package com.scrolless.app.di import android.content.Context +import com.scrolless.app.overlay.TimerOverlayManager +import com.scrolless.app.overlay.TimerOverlayManagerImpl import com.scrolless.app.provider.AppProvider import com.scrolless.app.provider.AppProviderImpl import com.scrolless.app.provider.NavigationProvider @@ -15,6 +17,8 @@ import com.scrolless.app.provider.ThemeProvider import com.scrolless.app.provider.ThemeProviderImpl import com.scrolless.app.provider.UsageTracker import com.scrolless.app.provider.UsageTrackerImpl +import com.scrolless.app.services.BlockController +import com.scrolless.app.services.BlockControllerImpl import dagger.Module import dagger.Provides import dagger.hilt.InstallIn @@ -52,4 +56,17 @@ class ProviderModule { fun provideUsageTrackerImpl( appProvider: AppProvider ): UsageTracker = UsageTrackerImpl(appProvider) + + @Provides + @Singleton + fun provideBlockController( + usageTracker: UsageTracker + ): BlockController = BlockControllerImpl(usageTracker) + + @Provides + @Singleton + fun provideTimerOverlayManager( + usageTracker: UsageTracker, + appProvider: AppProvider + ): TimerOverlayManager = TimerOverlayManagerImpl(usageTracker, appProvider) } diff --git a/app/src/main/java/com/scrolless/app/features/home/HomeFragment.kt b/app/src/main/java/com/scrolless/app/features/home/HomeFragment.kt index 4bdd1a2..2da3eb3 100644 --- a/app/src/main/java/com/scrolless/app/features/home/HomeFragment.kt +++ b/app/src/main/java/com/scrolless/app/features/home/HomeFragment.kt @@ -21,11 +21,8 @@ import com.scrolless.app.provider.AppProvider import com.scrolless.app.provider.UsageTracker import com.scrolless.app.services.ScrollessBlockAccessibilityService import com.scrolless.framework.extensions.* -import com.scrolless.framework.extensions.formatTime -import com.scrolless.framework.extensions.observeFlow import dagger.hilt.android.AndroidEntryPoint import javax.inject.Inject - @AndroidEntryPoint class HomeFragment : BaseFragment() { companion object { @@ -40,6 +37,7 @@ class HomeFragment : BaseFragment() { lateinit var usageTracker: UsageTracker override fun onViewReady(bundle: Bundle?) { + observeFlow(appProvider.blockConfigFlow) { config -> updateUIForBlockOption(config.blockOption) updateInfoText(usageTracker.dailyUsageInMemory, config.timeLimit) @@ -128,6 +126,23 @@ class HomeFragment : BaseFragment() { if (!isAccessibilityServiceEnabled(requireContext())) { openAccessibilitySettings(requireContext()) } + + setTimerOverlayCheckBoxListener() + } + + /** + * Sets up the listener for the timer overlay checkbox. + * + * Initializes the checkbox state from appProvider.timerOverlayEnabled + * and updates it when the checkbox is toggled. + */ + private fun setTimerOverlayCheckBoxListener() { + + binding.checkBoxTimerOverlay.isChecked = appProvider.timerOverlayEnabled + + binding.checkBoxTimerOverlay.setOnCheckedChangeListener { _, isChecked -> + appProvider.timerOverlayEnabled = isChecked + } } override fun onResume() { diff --git a/app/src/main/java/com/scrolless/app/overlay/TimerOverlayManager.kt b/app/src/main/java/com/scrolless/app/overlay/TimerOverlayManager.kt new file mode 100644 index 0000000..d177adf --- /dev/null +++ b/app/src/main/java/com/scrolless/app/overlay/TimerOverlayManager.kt @@ -0,0 +1,27 @@ +package com.scrolless.app.overlay + +import android.content.Context +import android.graphics.PixelFormat +import android.view.Gravity +import android.view.LayoutInflater +import android.view.MotionEvent +import android.view.View +import android.view.WindowManager +import android.widget.TextView +import androidx.appcompat.view.ContextThemeWrapper +import com.scrolless.app.R +import com.scrolless.app.provider.AppProvider +import com.scrolless.app.provider.UsageTracker +import com.scrolless.framework.extensions.getReadableTime +import kotlinx.coroutines.CoroutineScope +import kotlinx.coroutines.Dispatchers +import kotlinx.coroutines.Job +import kotlinx.coroutines.MainScope +import kotlinx.coroutines.delay +import kotlinx.coroutines.launch + +interface TimerOverlayManager { + fun attachServiceContext(context: Context) + fun show() + fun hide() +} diff --git a/app/src/main/java/com/scrolless/app/overlay/TimerOverlayManagerImpl.kt b/app/src/main/java/com/scrolless/app/overlay/TimerOverlayManagerImpl.kt new file mode 100644 index 0000000..39c2929 --- /dev/null +++ b/app/src/main/java/com/scrolless/app/overlay/TimerOverlayManagerImpl.kt @@ -0,0 +1,192 @@ +package com.scrolless.app.overlay + +import android.content.Context +import android.graphics.PixelFormat +import android.view.Gravity +import android.view.LayoutInflater +import android.view.MotionEvent +import android.view.View +import android.view.WindowManager +import android.widget.TextView +import androidx.appcompat.view.ContextThemeWrapper +import com.scrolless.app.R +import com.scrolless.app.provider.AppProvider +import com.scrolless.app.provider.UsageTracker +import com.scrolless.framework.extensions.fadeOutWithBounceAnimation +import com.scrolless.framework.extensions.getReadableTime +import jakarta.inject.Inject +import kotlinx.coroutines.CoroutineScope +import kotlinx.coroutines.Dispatchers +import kotlinx.coroutines.Job +import kotlinx.coroutines.SupervisorJob +import kotlinx.coroutines.delay +import kotlinx.coroutines.launch +import kotlinx.coroutines.isActive + +/** + * Implementation of TimerOverlayManager that displays a timer overlay on top of the brain rot content. + */ +class TimerOverlayManagerImpl @Inject constructor( + private val usageTracker: UsageTracker, + private val appProvider: AppProvider +) : TimerOverlayManager { + + private var overlayView: View? = null + private var timerTextView: TextView? = null + + // Job for updating the timer text + private var timerUpdateJob: Job? = null + + private lateinit var layoutParams: WindowManager.LayoutParams + + // Store the last known overlay position (persisted in appProvider) + private var positionX = appProvider.timerOverlayPositionX + private var positionY = appProvider.timerOverlayPositionY + + private val coroutineScope = CoroutineScope(Dispatchers.Main + SupervisorJob()) + + private lateinit var serviceContext: Context + private var windowManager: WindowManager? = null + + /** + * Attaches the service context used to interact with the window manager. + */ + override fun attachServiceContext(context: Context) { + serviceContext = context + windowManager = serviceContext.getSystemService(Context.WINDOW_SERVICE) as WindowManager + } + + /** + * Displays the timer overlay on screen, if it's not already visible. + */ + override fun show() { + + // If already showing, do nothing + if (overlayView != null) return + + // Offset the start time by 1 second to account for delays. + val startTimeMillis = System.currentTimeMillis() - 1000 + + // Inflate the overlay view using the app's theme + val themedContext = ContextThemeWrapper(serviceContext, R.style.AppTheme) + val inflater = LayoutInflater.from(themedContext) + overlayView = inflater.inflate(R.layout.overlay_timer, null) + timerTextView = overlayView?.findViewById(R.id.timerTextView) + + // Set up layout parameters for the overlay window + layoutParams = WindowManager.LayoutParams( + WindowManager.LayoutParams.WRAP_CONTENT, + WindowManager.LayoutParams.WRAP_CONTENT, + WindowManager.LayoutParams.TYPE_ACCESSIBILITY_OVERLAY, + WindowManager.LayoutParams.FLAG_NOT_FOCUSABLE or WindowManager.LayoutParams.FLAG_LAYOUT_IN_SCREEN, + PixelFormat.TRANSLUCENT, + ).apply { + gravity = Gravity.TOP or Gravity.END + x = positionX + y = positionY + } + + // Start invisible for a quick fade-in effect. + overlayView?.alpha = 0f + windowManager?.addView(overlayView, layoutParams) + + overlayView?.animate() + ?.alpha(1f) + ?.setDuration(200) + ?.start() + + enableDragging() + startTimer(startTimeMillis) + } + + /** + * Hides the timer overlay with an animation, then removes it from the window. + */ + override fun hide() { + stopTimer() + + val currentView = overlayView ?: return + + // Update the timer text with the total daily usage before hiding. + val totalTime = usageTracker.getDailyUsage() + timerTextView?.text = totalTime.getReadableTime() + + currentView.fadeOutWithBounceAnimation { + currentView.visibility = View.GONE + windowManager?.removeView(currentView) + overlayView = null + timerTextView = null + } + } + + /** + * Starts the coroutine to update the timer text every second. + * + * @param startTimeMillis The starting point for the timer + */ + private fun startTimer(startTimeMillis: Long) { + + // Cancel any existing timer before starting a new one + stopTimer() + + timerUpdateJob = coroutineScope.launch(Dispatchers.Main) { + while (isActive) { + val elapsed = System.currentTimeMillis() - startTimeMillis + timerTextView?.text = elapsed.getReadableTime() + delay(1000) + } + } + } + + /** + * Cancels any active timer job. + */ + private fun stopTimer() { + timerUpdateJob?.cancel() + timerUpdateJob = null + } + + + /** + * Enables dragging of the overlay view, updating and persisting position to [appProvider]. + */ + private fun enableDragging() { + overlayView?.setOnTouchListener( + object : View.OnTouchListener { + private var initialX = 0 + private var initialY = 0 + private var initialTouchX = 0f + private var initialTouchY = 0f + + override fun onTouch(view: View, motionEvent: MotionEvent): Boolean { + when (motionEvent.action) { + MotionEvent.ACTION_DOWN -> { + initialX = layoutParams.x + initialY = layoutParams.y + initialTouchX = motionEvent.rawX + initialTouchY = motionEvent.rawY + return true + } + + MotionEvent.ACTION_MOVE -> { + layoutParams.x = initialX - (motionEvent.rawX - initialTouchX).toInt() + layoutParams.y = initialY + (motionEvent.rawY - initialTouchY).toInt() + windowManager?.updateViewLayout(overlayView, layoutParams) + return true + } + + MotionEvent.ACTION_UP -> { + positionX = layoutParams.x + positionY = layoutParams.y + + // Persist final position to appProvider + appProvider.timerOverlayPositionX = positionX + appProvider.timerOverlayPositionY = positionY + } + } + return false + } + }, + ) + } +} \ No newline at end of file diff --git a/app/src/main/java/com/scrolless/app/provider/AppProvider.kt b/app/src/main/java/com/scrolless/app/provider/AppProvider.kt index b79e0f6..2115103 100644 --- a/app/src/main/java/com/scrolless/app/provider/AppProvider.kt +++ b/app/src/main/java/com/scrolless/app/provider/AppProvider.kt @@ -20,4 +20,8 @@ interface AppProvider { */ var blockConfig: BlockConfig val blockConfigFlow: StateFlow + + var timerOverlayEnabled: Boolean + var timerOverlayPositionX: Int + var timerOverlayPositionY: Int } diff --git a/app/src/main/java/com/scrolless/app/provider/AppProviderImpl.kt b/app/src/main/java/com/scrolless/app/provider/AppProviderImpl.kt index 9a0a56c..3d0b8e6 100644 --- a/app/src/main/java/com/scrolless/app/provider/AppProviderImpl.kt +++ b/app/src/main/java/com/scrolless/app/provider/AppProviderImpl.kt @@ -22,6 +22,11 @@ class AppProviderImpl(context: Context) : AppProvider { private const val PREF_INTERVAL_LENGTH = "interval_length" private const val PREF_LAST_RESET_DAY = "last_reset_day" private const val PREF_TOTAL_DAILY_USAGE = "total_daily_usage" + + // Timer overlay + private const val PREF_TIMER_OVERLAY_ENABLED = "timer_overlay_enabled" + private const val PREF_TIMER_POSITION_X = "timer_overlay_position_x" + private const val PREF_TIMER_POSITION_Y = "timer_overlay_position_y" } override val cacheManager = CacheManager(context, PREF_PACKAGE_NAME) @@ -61,6 +66,18 @@ class AppProviderImpl(context: Context) : AppProvider { get() = cacheManager.read(PREF_TOTAL_DAILY_USAGE, 0) set(value) = cacheManager.write(PREF_TOTAL_DAILY_USAGE, value) + override var timerOverlayEnabled: Boolean + get() = cacheManager.read(PREF_TIMER_OVERLAY_ENABLED, false) + set(value) = cacheManager.write(PREF_TIMER_OVERLAY_ENABLED, value) + + override var timerOverlayPositionX: Int + get() = cacheManager.read(PREF_TIMER_POSITION_X, 16) + set(value) = cacheManager.write(PREF_TIMER_POSITION_X, value) + + override var timerOverlayPositionY: Int + get() = cacheManager.read(PREF_TIMER_POSITION_Y, 16) + set(value) = cacheManager.write(PREF_TIMER_POSITION_Y, value) + private fun readBlockConfigFromCache(): BlockConfig { val blockOption = cacheManager.read(PREF_BLOCK_OPTION, BlockOption.NothingSelected) val timeLimit = cacheManager.read(PREF_TIME_LIMIT, 20000L) diff --git a/app/src/main/java/com/scrolless/app/provider/UsageTrackerImpl.kt b/app/src/main/java/com/scrolless/app/provider/UsageTrackerImpl.kt index 2c2b5a7..ecec796 100644 --- a/app/src/main/java/com/scrolless/app/provider/UsageTrackerImpl.kt +++ b/app/src/main/java/com/scrolless/app/provider/UsageTrackerImpl.kt @@ -47,6 +47,7 @@ class UsageTrackerImpl @Inject constructor( if (currentDay != lastDay) { dailyUsageInMemory = 0L appProvider.lastResetDay = currentDay + save() } } diff --git a/app/src/main/java/com/scrolless/app/services/BlockController.kt b/app/src/main/java/com/scrolless/app/services/BlockController.kt index 8d68eb6..044b749 100644 --- a/app/src/main/java/com/scrolless/app/services/BlockController.kt +++ b/app/src/main/java/com/scrolless/app/services/BlockController.kt @@ -5,60 +5,36 @@ package com.scrolless.app.services import com.scrolless.app.features.home.BlockConfig -import com.scrolless.app.features.home.BlockOption -import com.scrolless.app.provider.AppProvider -import com.scrolless.app.provider.UsageTracker -import com.scrolless.app.services.handlers.BlockAllBlockHandler -import com.scrolless.app.services.handlers.DayLimitBlockHandler -import com.scrolless.app.services.handlers.IntervalTimerBlockHandler -import com.scrolless.app.services.handlers.NothingSelectedBlockHandler -import com.scrolless.app.services.handlers.TemporaryUnblockBlockHandler -class BlockController( - private val appProvider: AppProvider, - private val usageTracker: UsageTracker -) { - - private var handler: BlockOptionHandler = createHandlerForConfig(appProvider.blockConfig) +interface BlockController { /** - * Updates the block option, creating a new handler and performing any necessary resets. + * Initializes the controller with a configuration. + * + * @param blockConfig Configuration for blocking options. */ - fun setBlockConfigOption(newOption: BlockConfig) { - handler = createHandlerForConfig(newOption) - initializeForOption() // Re-run initialization logic if needed - } - - private fun createHandlerForConfig(config: BlockConfig): BlockOptionHandler = when (config.blockOption) { - BlockOption.BlockAll -> BlockAllBlockHandler() - BlockOption.DayLimit -> DayLimitBlockHandler(config.timeLimit) - BlockOption.IntervalTimer -> IntervalTimerBlockHandler( - config.timeLimit, - config.intervalLength, - ) - - BlockOption.TemporaryUnblock -> TemporaryUnblockBlockHandler(config.timeLimit) - BlockOption.NothingSelected -> NothingSelectedBlockHandler() - } + fun init(blockConfig: BlockConfig) - fun onEnterBlockedContent(): Boolean { - usageTracker.checkDailyReset() - - // If handler says block now, return true - return handler.onEnterContent(usageTracker) - } - - fun onPeriodicCheck(elapsedTime: Long): Boolean = handler.onPeriodicCheck(usageTracker, elapsedTime) + /** + * Called when entering brain rot content. + * + * @return `true` if blocking is required. + */ + fun onEnterBlockedContent(): Boolean - fun onExitBlockedContent(sessionTime: Long) { - handler.onExitContent(usageTracker, sessionTime) - } + /** + * Called periodically to perform any checks on the handler + * for example to check if any time limit has been reached. + * + * @param elapsedTime Time elapsed since the last check. + * @return `true` if the content should remain blocked. + */ + fun onPeriodicCheck(elapsedTime: Long): Boolean - fun initializeForOption() { - usageTracker.load() // Make sure we have the latest data - usageTracker.checkDailyReset() // In case day changed - if (appProvider.blockConfig.blockOption == BlockOption.IntervalTimer) { - usageTracker.checkDailyReset() // or do any additional interval logic here - } - } + /** + * Called when exiting brain rot content. + * + * @param sessionTime Duration of the session. + */ + fun onExitBlockedContent(sessionTime: Long) } diff --git a/app/src/main/java/com/scrolless/app/services/BlockControllerImpl.kt b/app/src/main/java/com/scrolless/app/services/BlockControllerImpl.kt new file mode 100644 index 0000000..9d73124 --- /dev/null +++ b/app/src/main/java/com/scrolless/app/services/BlockControllerImpl.kt @@ -0,0 +1,93 @@ +/* + * Copyright (C) 2025, Scrolless + * All rights reserved. + */ +package com.scrolless.app.services + +import com.scrolless.app.features.home.BlockConfig +import com.scrolless.app.features.home.BlockOption +import com.scrolless.app.provider.UsageTracker +import com.scrolless.app.services.handlers.BlockAllBlockHandler +import com.scrolless.app.services.handlers.DayLimitBlockHandler +import com.scrolless.app.services.handlers.IntervalTimerBlockHandler +import com.scrolless.app.services.handlers.NothingSelectedBlockHandler +import com.scrolless.app.services.handlers.TemporaryUnblockBlockHandler + +/** + * Implementation of [BlockController] that uses a [BlockOptionHandler] to handle blocking logic. + * + * @property usageTracker Tracks usage data and handles daily resets. + */ +class BlockControllerImpl( + private val usageTracker: UsageTracker +) : BlockController { + + private lateinit var handler: BlockOptionHandler + + /** + * Initializes the controller with a configuration. + * Sets up the correct handler and refreshes usage data. + * + * @param blockConfig Configuration for blocking options. + */ + override fun init(blockConfig: BlockConfig) { + + handler = createHandlerForConfig(blockConfig) + usageTracker.load() // Make sure we have the latest data + usageTracker.checkDailyReset() // In case day changed + if (blockConfig.blockOption == BlockOption.IntervalTimer) { + usageTracker.checkDailyReset() // or do any additional interval logic here + } + } + + /** + * Chooses a [BlockOptionHandler] based on the [BlockConfig]. + * + * @param config The blocking configuration. + * @return A handler matching the configuration. + */ + private fun createHandlerForConfig(config: BlockConfig): BlockOptionHandler = + when (config.blockOption) { + BlockOption.BlockAll -> BlockAllBlockHandler() + BlockOption.DayLimit -> DayLimitBlockHandler(config.timeLimit) + BlockOption.IntervalTimer -> IntervalTimerBlockHandler( + config.timeLimit, + config.intervalLength, + ) + + BlockOption.TemporaryUnblock -> TemporaryUnblockBlockHandler(config.timeLimit) + BlockOption.NothingSelected -> NothingSelectedBlockHandler() + } + + /** + * Called when entering brain rot content. + * Checks usage and decides if the content should be immediately blocked. + * + * @return `true` if blocking is required. + */ + override fun onEnterBlockedContent(): Boolean { + usageTracker.checkDailyReset() + + // If handler says block now, return true + return handler.onEnterContent(usageTracker) + } + + /** + * Called periodically to perform any checks on the handler + * for example to check if any time limit has been reached. + * + * @param elapsedTime Time elapsed since the last check. + * @return `true` if the content should remain blocked. + */ + override fun onPeriodicCheck(elapsedTime: Long): Boolean = + handler.onPeriodicCheck(usageTracker, elapsedTime) + + /** + * Called when exiting brain rot content. + * + * @param sessionTime Duration of the session. + */ + override fun onExitBlockedContent(sessionTime: Long) { + handler.onExitContent(usageTracker, sessionTime) + } +} diff --git a/app/src/main/java/com/scrolless/app/services/ScrollessBlockAccessibilityService.kt b/app/src/main/java/com/scrolless/app/services/ScrollessBlockAccessibilityService.kt index 7c6de3e..e170818 100644 --- a/app/src/main/java/com/scrolless/app/services/ScrollessBlockAccessibilityService.kt +++ b/app/src/main/java/com/scrolless/app/services/ScrollessBlockAccessibilityService.kt @@ -12,6 +12,8 @@ import android.os.Looper import android.view.accessibility.AccessibilityEvent import android.view.accessibility.AccessibilityNodeInfo import com.scrolless.app.features.home.BlockOption +import com.scrolless.app.overlay.TimerOverlayManager +import com.scrolless.app.overlay.TimerOverlayManagerImpl import com.scrolless.app.provider.AppProvider import com.scrolless.app.provider.UsageTracker import dagger.hilt.android.AndroidEntryPoint @@ -42,16 +44,20 @@ class ScrollessBlockAccessibilityService : AccessibilityService() { @Inject lateinit var usageTracker: UsageTracker - private val blockController by lazy { BlockController(appProvider, usageTracker) } + @Inject + lateinit var blockController: BlockController - private var currentOnVideos = false - private var timeStartOnBrainRot: Long = 0L + @Inject + lateinit var timerOverlayManager: TimerOverlayManager private var active = true + private var currentOnVideos = false + private var timeStartOnBrainRot: Long = 0L private val blockedViews = setOf( "com.google.android.youtube:id/reel_player_page_container", "com.instagram.android:id/clips_viewer_view_pager", + // TODO Tiktok is not supported yet ) private val videoCheckRunnable = object : Runnable { @@ -68,16 +74,17 @@ class ScrollessBlockAccessibilityService : AccessibilityService() { } override fun onServiceConnected() { + super.onServiceConnected() configureServiceInfo() - // Initialize block logic - blockController.initializeForOption() + // Make sure the timer overlay manager has the service's context + timerOverlayManager.attachServiceContext(this) // Observe changes to the block config serviceScope.launch { appProvider.blockConfigFlow.collect { newConfig -> - blockController.setBlockConfigOption(newConfig) + blockController.init(newConfig) active = (newConfig.blockOption != BlockOption.NothingSelected) } } @@ -100,8 +107,7 @@ class ScrollessBlockAccessibilityService : AccessibilityService() { } } - override fun onInterrupt() { - } + override fun onInterrupt() = Unit override fun onDestroy() { super.onDestroy() @@ -113,22 +119,33 @@ class ScrollessBlockAccessibilityService : AccessibilityService() { } private fun configureServiceInfo() { + val info = AccessibilityServiceInfo().apply { + eventTypes = AccessibilityEvent.TYPE_WINDOW_STATE_CHANGED feedbackType = AccessibilityServiceInfo.FEEDBACK_GENERIC notificationTimeout = 100 + flags = - AccessibilityServiceInfo.FLAG_REPORT_VIEW_IDS or AccessibilityServiceInfo.FLAG_RETRIEVE_INTERACTIVE_WINDOWS + AccessibilityServiceInfo.FLAG_REPORT_VIEW_IDS or + AccessibilityServiceInfo.FLAG_RETRIEVE_INTERACTIVE_WINDOWS } serviceInfo = info } - private fun detectBlockedContent(rootNode: AccessibilityNodeInfo): Boolean = blockedViews.any { id -> - rootNode.findAccessibilityNodeInfosByViewId(id).isNotEmpty() - } + private fun detectBlockedContent(rootNode: AccessibilityNodeInfo): Boolean = + blockedViews.any { id -> + rootNode.findAccessibilityNodeInfosByViewId(id).isNotEmpty() + } private fun onBlockedContentEntered() { if (!currentOnVideos) { + + // If timer overlay is enabled, show it + if (appProvider.timerOverlayEnabled) { + timerOverlayManager.show() + } + currentOnVideos = true timeStartOnBrainRot = System.currentTimeMillis() startPeriodicCheck() @@ -143,7 +160,9 @@ class ScrollessBlockAccessibilityService : AccessibilityService() { } private fun onBlockedContentExited() { + if (currentOnVideos) { + val sessionTime = System.currentTimeMillis() - timeStartOnBrainRot // Add to usage in memory @@ -154,6 +173,12 @@ class ScrollessBlockAccessibilityService : AccessibilityService() { currentOnVideos = false stopPeriodicCheck() + + if (appProvider.timerOverlayEnabled) { + timerOverlayManager.hide() + } + + usageTracker.save() } } diff --git a/app/src/main/java/com/scrolless/app/services/handlers/DayLimitBlockHandler.kt b/app/src/main/java/com/scrolless/app/services/handlers/DayLimitBlockHandler.kt index 6e3f6bc..1c00235 100644 --- a/app/src/main/java/com/scrolless/app/services/handlers/DayLimitBlockHandler.kt +++ b/app/src/main/java/com/scrolless/app/services/handlers/DayLimitBlockHandler.kt @@ -18,7 +18,5 @@ class DayLimitBlockHandler(private val timeLimit: Long) : BlockOptionHandler { return (usageTracker.getDailyUsage() + elapsedTime) >= timeLimit } - override fun onExitContent(usageTracker: UsageTracker, sessionTime: Long) { - usageTracker.addToDailyUsage(sessionTime) - } + override fun onExitContent(usageTracker: UsageTracker, sessionTime: Long) {} } diff --git a/app/src/main/res/layout/fragment_home.xml b/app/src/main/res/layout/fragment_home.xml index 3fd4b87..3b132d8 100644 --- a/app/src/main/res/layout/fragment_home.xml +++ b/app/src/main/res/layout/fragment_home.xml @@ -147,16 +147,26 @@ app:layout_constraintStart_toEndOf="@id/temporary_unblock_button" app:layout_constraintTop_toTopOf="@id/temporary_unblock_button" /> + +