feat: notification dots on app icons
New NotificationBadgeService (NotificationListenerService) tracks pending, dismissible notifications per app. Home, favorite, and drawer icons get a red dot in the top-right corner when the app has active notifications. Settings: 'Notification Dots' toggle under Display features; enabling it opens the system notification-access screen when permission is missing. Dots update live via a StateFlow observed by the fragments.
This commit is contained in:
@@ -103,6 +103,15 @@
|
||||
<action android:name="android.accessibilityservice.AccessibilityService" />
|
||||
</intent-filter>
|
||||
</service>
|
||||
<service
|
||||
android:name=".service.NotificationBadgeService"
|
||||
android:exported="true"
|
||||
android:label="@string/notification_badge_service_label"
|
||||
android:permission="android.permission.BIND_NOTIFICATION_LISTENER_SERVICE">
|
||||
<intent-filter>
|
||||
<action android:name="android.service.notification.NotificationListenerService" />
|
||||
</intent-filter>
|
||||
</service>
|
||||
<provider
|
||||
android:name="androidx.core.content.FileProvider"
|
||||
android:authorities="${applicationId}.provider"
|
||||
|
||||
@@ -198,6 +198,10 @@ class PreferenceHelper @Inject constructor(@ApplicationContext context: Context)
|
||||
get() = prefs.getBoolean(Constants.DISABLE_ANIMATIONS, false)
|
||||
set(value) = prefs.edit().putBoolean(Constants.DISABLE_ANIMATIONS, value).apply()
|
||||
|
||||
var showNotificationDots: Boolean
|
||||
get() = prefs.getBoolean(Constants.SHOW_NOTIFICATION_DOTS, false)
|
||||
set(value) = prefs.edit().putBoolean(Constants.SHOW_NOTIFICATION_DOTS, value).apply()
|
||||
|
||||
var searchEngines: Constants.SearchEngines
|
||||
get() {
|
||||
return try {
|
||||
|
||||
@@ -0,0 +1,76 @@
|
||||
package com.github.droidworksstudio.launcher.service
|
||||
|
||||
import android.app.Notification
|
||||
import android.service.notification.NotificationListenerService
|
||||
import android.service.notification.StatusBarNotification
|
||||
import kotlinx.coroutines.flow.MutableStateFlow
|
||||
import kotlinx.coroutines.flow.StateFlow
|
||||
|
||||
/**
|
||||
* Tracks pending (dismissible) notifications per app so the launcher can show
|
||||
* a red dot on app icons.
|
||||
*
|
||||
* The user must grant notification access in the system settings
|
||||
* (Settings.ACTION_NOTIFICATION_LISTENER_SETTINGS) before this service
|
||||
* receives any events. Android rebinds it automatically after every boot.
|
||||
*/
|
||||
class NotificationBadgeService : NotificationListenerService() {
|
||||
|
||||
/** Notification key -> "userId/packageName". Keyed to dedupe updates. */
|
||||
private val keyToPackage = HashMap<String, String>()
|
||||
|
||||
override fun onListenerConnected() {
|
||||
super.onListenerConnected()
|
||||
synchronized(keyToPackage) {
|
||||
keyToPackage.clear()
|
||||
activeNotifications.forEach { sbn ->
|
||||
if (isCounted(sbn)) keyToPackage[sbn.key] = userIdKey(sbn)
|
||||
}
|
||||
publish()
|
||||
}
|
||||
}
|
||||
|
||||
override fun onNotificationPosted(sbn: StatusBarNotification) {
|
||||
if (!isCounted(sbn)) return
|
||||
synchronized(keyToPackage) {
|
||||
// A posted notification with an existing key is just an update.
|
||||
if (keyToPackage.containsKey(sbn.key)) return
|
||||
keyToPackage[sbn.key] = userIdKey(sbn)
|
||||
publish()
|
||||
}
|
||||
}
|
||||
|
||||
override fun onNotificationRemoved(sbn: StatusBarNotification) {
|
||||
synchronized(keyToPackage) {
|
||||
if (keyToPackage.remove(sbn.key) != null) publish()
|
||||
}
|
||||
}
|
||||
|
||||
/** Only swipe-away, non-ongoing notifications get a dot. */
|
||||
private fun isCounted(sbn: StatusBarNotification): Boolean {
|
||||
val notification = sbn.notification
|
||||
if (!sbn.isClearable) return false
|
||||
if (notification.flags and Notification.FLAG_ONGOING_EVENT != 0) return false
|
||||
return true
|
||||
}
|
||||
|
||||
private fun userIdKey(sbn: StatusBarNotification) =
|
||||
"${sbn.userId}/" + sbn.packageName
|
||||
|
||||
private fun publish() {
|
||||
val counts = HashMap<String, Int>()
|
||||
keyToPackage.values.forEach { key ->
|
||||
counts[key] = (counts[key] ?: 0) + 1
|
||||
}
|
||||
notificationCounts.value = counts
|
||||
}
|
||||
|
||||
companion object {
|
||||
/** Active notification count per app, keyed by "userId/packageName". */
|
||||
val notificationCounts: MutableStateFlow<Map<String, Int>> =
|
||||
MutableStateFlow(emptyMap())
|
||||
|
||||
fun hasNotifications(userId: Int, packageName: String): Boolean =
|
||||
notificationCounts.value["$userId/$packageName"]?.let { it > 0 } ?: false
|
||||
}
|
||||
}
|
||||
@@ -40,6 +40,7 @@ import com.github.droidworksstudio.launcher.helper.PreferenceHelper
|
||||
import com.github.droidworksstudio.launcher.listener.OnItemClickedListener
|
||||
import com.github.droidworksstudio.launcher.listener.OnSwipeTouchListener
|
||||
import com.github.droidworksstudio.launcher.listener.ScrollEventListener
|
||||
import com.github.droidworksstudio.launcher.service.NotificationBadgeService
|
||||
import kotlin.math.roundToInt
|
||||
import com.github.droidworksstudio.launcher.ui.bottomsheetdialog.AppInfoBottomSheetFragment
|
||||
import com.github.droidworksstudio.launcher.utils.Constants
|
||||
@@ -111,6 +112,7 @@ class DrawFragment : Fragment(),
|
||||
observeClickListener()
|
||||
observeSwipeTouchListener()
|
||||
observeScrollTouchListener()
|
||||
observeNotificationBadges()
|
||||
|
||||
// Initialize observation of drawer apps
|
||||
observeDrawerApps()
|
||||
@@ -166,6 +168,25 @@ class DrawFragment : Fragment(),
|
||||
}
|
||||
}
|
||||
|
||||
private fun observeNotificationBadges() {
|
||||
viewLifecycleOwner.lifecycleScope.launch {
|
||||
repeatOnLifecycle(Lifecycle.State.CREATED) {
|
||||
NotificationBadgeService.notificationCounts.collect {
|
||||
val recyclerView = binding.drawAdapter
|
||||
for (i in 0 until recyclerView.childCount) {
|
||||
val holder = recyclerView.getChildViewHolder(recyclerView.getChildAt(i))
|
||||
if (holder is DrawViewHolder) {
|
||||
val position = holder.bindingAdapterPosition
|
||||
if (position != RecyclerView.NO_POSITION) {
|
||||
holder.bind(drawAdapter.currentList[position])
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@RequiresApi(Build.VERSION_CODES.R)
|
||||
private fun observeDrawerApps() {
|
||||
// Start comparing installed app information
|
||||
|
||||
@@ -14,7 +14,9 @@ import com.github.droidworksstudio.launcher.data.entities.AppInfo
|
||||
import com.github.droidworksstudio.launcher.databinding.ItemDrawBinding
|
||||
import com.github.droidworksstudio.launcher.helper.PreferenceHelper
|
||||
import com.github.droidworksstudio.launcher.listener.OnItemClickedListener
|
||||
import com.github.droidworksstudio.launcher.service.NotificationBadgeService
|
||||
import com.github.droidworksstudio.launcher.utils.Constants
|
||||
import com.github.droidworksstudio.launcher.utils.NotificationDotHelper
|
||||
|
||||
class DrawViewHolder(
|
||||
private val binding: ItemDrawBinding,
|
||||
@@ -70,7 +72,20 @@ class DrawViewHolder(
|
||||
}
|
||||
|
||||
appDrawIcon.layoutParams = layoutParams
|
||||
appDrawIcon.setImageDrawable(appNewIcon ?: nonNullDrawable)
|
||||
|
||||
val baseIcon: Drawable = appNewIcon ?: nonNullDrawable
|
||||
appDrawIcon.setImageDrawable(
|
||||
if (preferenceHelper.showNotificationDots &&
|
||||
NotificationBadgeService.hasNotifications(
|
||||
appInfo.userHandle,
|
||||
appInfo.packageName
|
||||
)
|
||||
) {
|
||||
NotificationDotHelper.withDot(itemView.context, baseIcon)
|
||||
} else {
|
||||
baseIcon
|
||||
}
|
||||
)
|
||||
appDrawIcon.visibility = View.VISIBLE
|
||||
|
||||
val parentLayout = appDrawName.parent as LinearLayoutCompat
|
||||
|
||||
@@ -11,7 +11,9 @@ import android.view.ViewGroup
|
||||
import androidx.annotation.RequiresApi
|
||||
import androidx.fragment.app.Fragment
|
||||
import androidx.fragment.app.viewModels
|
||||
import androidx.lifecycle.Lifecycle
|
||||
import androidx.lifecycle.lifecycleScope
|
||||
import androidx.lifecycle.repeatOnLifecycle
|
||||
import androidx.navigation.fragment.findNavController
|
||||
import androidx.recyclerview.widget.ItemTouchHelper
|
||||
import androidx.recyclerview.widget.RecyclerView
|
||||
@@ -28,6 +30,8 @@ import com.github.droidworksstudio.launcher.helper.PreferenceHelper
|
||||
import com.github.droidworksstudio.launcher.listener.OnItemClickedListener
|
||||
import com.github.droidworksstudio.launcher.listener.OnItemMoveListener
|
||||
import com.github.droidworksstudio.launcher.listener.OnSwipeTouchListener
|
||||
import com.github.droidworksstudio.launcher.service.NotificationBadgeService
|
||||
import com.github.droidworksstudio.launcher.ui.favorite.FavoriteViewHolder
|
||||
import com.github.droidworksstudio.launcher.viewmodel.AppViewModel
|
||||
import dagger.hilt.android.AndroidEntryPoint
|
||||
import kotlinx.coroutines.launch
|
||||
@@ -82,6 +86,7 @@ class FavoriteFragment : Fragment(),
|
||||
setupRecyclerView()
|
||||
observeFavorite()
|
||||
observeHomeAppOrder()
|
||||
observeNotificationBadges()
|
||||
observeSwipeTouchListener()
|
||||
}
|
||||
|
||||
@@ -128,6 +133,25 @@ class FavoriteFragment : Fragment(),
|
||||
}
|
||||
}
|
||||
|
||||
private fun observeNotificationBadges() {
|
||||
viewLifecycleOwner.lifecycleScope.launch {
|
||||
repeatOnLifecycle(Lifecycle.State.CREATED) {
|
||||
NotificationBadgeService.notificationCounts.collect {
|
||||
val recyclerView = binding.favoriteAdapter
|
||||
for (i in 0 until recyclerView.childCount) {
|
||||
val holder = recyclerView.getChildViewHolder(recyclerView.getChildAt(i))
|
||||
if (holder is FavoriteViewHolder) {
|
||||
val position = holder.bindingAdapterPosition
|
||||
if (position != RecyclerView.NO_POSITION) {
|
||||
holder.bind(favoriteAdapter.currentList[position])
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private fun observeHomeAppOrder() {
|
||||
binding.favoriteAdapter.adapter = favoriteAdapter
|
||||
val listener: OnItemMoveListener.OnItemActionListener = favoriteAdapter
|
||||
|
||||
@@ -11,6 +11,8 @@ import com.github.droidworksstudio.launcher.data.entities.AppInfo
|
||||
import com.github.droidworksstudio.launcher.databinding.ItemFavoriteBinding
|
||||
import com.github.droidworksstudio.launcher.helper.PreferenceHelper
|
||||
import com.github.droidworksstudio.launcher.listener.OnItemClickedListener
|
||||
import com.github.droidworksstudio.launcher.service.NotificationBadgeService
|
||||
import com.github.droidworksstudio.launcher.utils.NotificationDotHelper
|
||||
|
||||
@SuppressLint("ClickableViewAccessibility")
|
||||
class FavoriteViewHolder(
|
||||
@@ -48,7 +50,18 @@ class FavoriteViewHolder(
|
||||
if (preferenceHelper.showAppIcon) {
|
||||
val appIcon =
|
||||
binding.root.context.packageManager.getApplicationIcon(appInfo.packageName)
|
||||
appFavoriteLeftIcon.setImageDrawable(appIcon)
|
||||
val baseIcon: android.graphics.drawable.Drawable =
|
||||
if (preferenceHelper.showNotificationDots &&
|
||||
NotificationBadgeService.hasNotifications(
|
||||
appInfo.userHandle,
|
||||
appInfo.packageName
|
||||
)
|
||||
) {
|
||||
NotificationDotHelper.withDot(binding.root.context, appIcon)
|
||||
} else {
|
||||
appIcon
|
||||
}
|
||||
appFavoriteLeftIcon.setImageDrawable(baseIcon)
|
||||
appFavoriteLeftIcon.layoutParams.width =
|
||||
preferenceHelper.appTextSize.toInt() * 3
|
||||
appFavoriteLeftIcon.layoutParams.height =
|
||||
|
||||
@@ -24,7 +24,9 @@ import androidx.biometric.BiometricPrompt
|
||||
import androidx.core.content.ContextCompat
|
||||
import androidx.fragment.app.Fragment
|
||||
import androidx.fragment.app.viewModels
|
||||
import androidx.lifecycle.Lifecycle
|
||||
import androidx.lifecycle.lifecycleScope
|
||||
import androidx.lifecycle.repeatOnLifecycle
|
||||
import androidx.navigation.NavOptions
|
||||
import androidx.navigation.fragment.findNavController
|
||||
import androidx.recyclerview.widget.StaggeredGridLayoutManager
|
||||
@@ -46,6 +48,7 @@ import com.github.droidworksstudio.launcher.helper.PreferenceHelper
|
||||
import com.github.droidworksstudio.launcher.listener.OnItemClickedListener
|
||||
import com.github.droidworksstudio.launcher.listener.OnSwipeTouchListener
|
||||
import com.github.droidworksstudio.launcher.listener.ScrollEventListener
|
||||
import com.github.droidworksstudio.launcher.service.NotificationBadgeService
|
||||
import com.github.droidworksstudio.launcher.ui.bottomsheetdialog.AppInfoBottomSheetFragment
|
||||
import com.github.droidworksstudio.launcher.utils.Constants
|
||||
import com.github.droidworksstudio.launcher.viewmodel.AppViewModel
|
||||
@@ -232,6 +235,25 @@ class HomeFragment : Fragment(),
|
||||
}
|
||||
}
|
||||
|
||||
private fun observeNotificationBadges() {
|
||||
viewLifecycleOwner.lifecycleScope.launch {
|
||||
repeatOnLifecycle(Lifecycle.State.CREATED) {
|
||||
NotificationBadgeService.notificationCounts.collect {
|
||||
val recyclerView = binding.appListAdapter
|
||||
for (i in 0 until recyclerView.childCount) {
|
||||
val holder = recyclerView.getChildViewHolder(recyclerView.getChildAt(i))
|
||||
if (holder is HomeViewHolder) {
|
||||
val position = holder.bindingAdapterPosition
|
||||
if (position != androidx.recyclerview.widget.RecyclerView.NO_POSITION) {
|
||||
holder.bind(homeAdapter.currentList[position])
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@SuppressLint("ClickableViewAccessibility", "InflateParams")
|
||||
private fun observeSwipeTouchListener() {
|
||||
binding.apply {
|
||||
@@ -584,6 +606,7 @@ class HomeFragment : Fragment(),
|
||||
binding.mainView.hideKeyboard()
|
||||
observeUserInterfaceSettings()
|
||||
observeFavoriteAppList()
|
||||
observeNotificationBadges()
|
||||
}
|
||||
|
||||
override fun onAppClicked(appInfo: AppInfo) {
|
||||
|
||||
@@ -15,7 +15,9 @@ import com.github.droidworksstudio.launcher.data.entities.AppInfo
|
||||
import com.github.droidworksstudio.launcher.databinding.ItemHomeBinding
|
||||
import com.github.droidworksstudio.launcher.helper.PreferenceHelper
|
||||
import com.github.droidworksstudio.launcher.listener.OnItemClickedListener
|
||||
import com.github.droidworksstudio.launcher.service.NotificationBadgeService
|
||||
import com.github.droidworksstudio.launcher.utils.Constants
|
||||
import com.github.droidworksstudio.launcher.utils.NotificationDotHelper
|
||||
import javax.inject.Inject
|
||||
|
||||
class HomeViewHolder @Inject constructor(
|
||||
@@ -70,7 +72,20 @@ class HomeViewHolder @Inject constructor(
|
||||
}
|
||||
|
||||
appHomeIcon.layoutParams = layoutParams
|
||||
appHomeIcon.setImageDrawable(appNewIcon ?: nonNullDrawable)
|
||||
|
||||
val baseIcon: Drawable = appNewIcon ?: nonNullDrawable
|
||||
appHomeIcon.setImageDrawable(
|
||||
if (preferenceHelper.showNotificationDots &&
|
||||
NotificationBadgeService.hasNotifications(
|
||||
appInfo.userHandle,
|
||||
appInfo.packageName
|
||||
)
|
||||
) {
|
||||
NotificationDotHelper.withDot(itemView.context, baseIcon)
|
||||
} else {
|
||||
baseIcon
|
||||
}
|
||||
)
|
||||
appHomeIcon.visibility = View.VISIBLE
|
||||
|
||||
val parentLayout = appHomeName.parent as LinearLayoutCompat
|
||||
|
||||
@@ -1,8 +1,11 @@
|
||||
package com.github.droidworksstudio.launcher.ui.settings
|
||||
|
||||
import android.content.Context
|
||||
import android.content.ComponentName
|
||||
import android.content.Intent
|
||||
import android.os.Build
|
||||
import android.os.Bundle
|
||||
import android.provider.Settings
|
||||
import android.view.Gravity
|
||||
import android.view.LayoutInflater
|
||||
import android.view.View
|
||||
@@ -18,6 +21,7 @@ import androidx.lifecycle.lifecycleScope
|
||||
import androidx.navigation.NavController
|
||||
import androidx.navigation.fragment.findNavController
|
||||
import com.github.droidworksstudio.common.getAppNameFromPackageName
|
||||
import com.github.droidworksstudio.common.showLongToast
|
||||
import com.github.droidworksstudio.launcher.R
|
||||
import com.github.droidworksstudio.launcher.databinding.FragmentSettingsFeaturesBinding
|
||||
import com.github.droidworksstudio.launcher.helper.AppHelper
|
||||
@@ -25,6 +29,7 @@ import com.github.droidworksstudio.launcher.helper.AppReloader
|
||||
import com.github.droidworksstudio.launcher.helper.PreferenceHelper
|
||||
import com.github.droidworksstudio.launcher.listener.ScrollEventListener
|
||||
import com.github.droidworksstudio.launcher.repository.AppInfoRepository
|
||||
import com.github.droidworksstudio.launcher.service.NotificationBadgeService
|
||||
import com.github.droidworksstudio.launcher.utils.Constants
|
||||
import com.github.droidworksstudio.launcher.viewmodel.PreferenceViewModel
|
||||
import com.google.android.material.dialog.MaterialAlertDialogBuilder
|
||||
@@ -129,6 +134,7 @@ class SettingsFeaturesFragment : Fragment(),
|
||||
homeAlignmentBottomSwitchCompat.isChecked = preferenceHelper.homeAlignmentBottom
|
||||
lockSettingsSwitchCompat.isChecked = preferenceHelper.settingsLock
|
||||
disableAnimationsSwitchCompat.isChecked = preferenceHelper.disableAnimations
|
||||
showNotificationDotsSwitchCompat.isChecked = preferenceHelper.showNotificationDots
|
||||
}
|
||||
}
|
||||
|
||||
@@ -450,10 +456,37 @@ class SettingsFeaturesFragment : Fragment(),
|
||||
val feedbackType = if (isChecked) "on" else "off"
|
||||
appHelper.triggerHapticFeedback(context, feedbackType)
|
||||
}
|
||||
|
||||
showNotificationDotsSwitchCompat.setOnCheckedChangeListener { _, isChecked ->
|
||||
preferenceViewModel.setShowNotificationDots(isChecked)
|
||||
val feedbackType = if (isChecked) "on" else "off"
|
||||
appHelper.triggerHapticFeedback(context, feedbackType)
|
||||
|
||||
if (isChecked && !isNotificationAccessGranted()) {
|
||||
openNotificationAccessSettings()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
private fun isNotificationAccessGranted(): Boolean {
|
||||
val componentName = ComponentName(requireContext(), NotificationBadgeService::class.java)
|
||||
val notificationManager =
|
||||
requireContext().getSystemService(Context.NOTIFICATION_SERVICE) as android.app.NotificationManager
|
||||
return notificationManager.isNotificationListenerAccessGranted(componentName)
|
||||
}
|
||||
|
||||
private fun openNotificationAccessSettings() {
|
||||
try {
|
||||
startActivity(Intent(Settings.ACTION_NOTIFICATION_LISTENER_SETTINGS))
|
||||
} catch (_: Exception) {
|
||||
requireContext().showLongToast(
|
||||
getString(R.string.toast_cannot_open_notification_access_settings)
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
private var swipeActionDialog: AlertDialog? = null
|
||||
|
||||
@RequiresApi(Build.VERSION_CODES.Q)
|
||||
|
||||
@@ -70,6 +70,7 @@ object Constants {
|
||||
const val HOME_ALLIGNMENT_BOTTOM = "HOME_ALLIGNMENT_BOTTOM"
|
||||
const val TOGGLE_SETTING_LOCK = "TOGGLE_SETTING_LOCK"
|
||||
const val DISABLE_ANIMATIONS = "DISABLE_ANIMATIONS"
|
||||
const val SHOW_NOTIFICATION_DOTS = "SHOW_NOTIFICATION_DOTS"
|
||||
|
||||
const val HOME_DATE_ALIGNMENT = "HOME_DATE_ALIGNMENT"
|
||||
const val HOME_TIME_ALIGNMENT = "HOME_TIME_ALIGNMENT"
|
||||
|
||||
@@ -0,0 +1,51 @@
|
||||
package com.github.droidworksstudio.launcher.utils
|
||||
|
||||
import android.content.Context
|
||||
import android.graphics.Bitmap
|
||||
import android.graphics.Canvas
|
||||
import android.graphics.Color
|
||||
import android.graphics.Paint
|
||||
import android.graphics.drawable.BitmapDrawable
|
||||
import android.graphics.drawable.Drawable
|
||||
import com.github.droidworksstudio.common.ColorIconsExtensions
|
||||
|
||||
object NotificationDotHelper {
|
||||
|
||||
/**
|
||||
* Returns a copy of [icon] with a red dot drawn in its top-right corner,
|
||||
* surrounded by a thin white border so it stays visible on busy icons.
|
||||
*/
|
||||
fun withDot(context: Context, icon: Drawable): Drawable {
|
||||
val source = ColorIconsExtensions.drawableToBitmap(icon)
|
||||
val width = source.width
|
||||
val height = source.height
|
||||
if (width <= 0 || height <= 0) return icon
|
||||
|
||||
val dotRadius = (minOf(width, height) * 0.22f).coerceAtLeast(4f)
|
||||
val centerX = width - dotRadius * 0.75f
|
||||
val centerY = dotRadius * 0.75f
|
||||
|
||||
val bitmap = Bitmap.createBitmap(width, height, Bitmap.Config.ARGB_8888)
|
||||
val canvas = Canvas(bitmap)
|
||||
canvas.drawBitmap(source, 0f, 0f, null)
|
||||
|
||||
val dotPaint = Paint(Paint.ANTI_ALIAS_FLAG).apply {
|
||||
color = Color.RED
|
||||
}
|
||||
canvas.drawCircle(centerX, centerY, dotRadius, dotPaint)
|
||||
|
||||
val borderPaint = Paint(Paint.ANTI_ALIAS_FLAG).apply {
|
||||
color = Color.WHITE
|
||||
style = Paint.Style.STROKE
|
||||
strokeWidth = (dotRadius * 0.28f).coerceAtLeast(1.5f)
|
||||
}
|
||||
canvas.drawCircle(
|
||||
centerX,
|
||||
centerY,
|
||||
dotRadius - borderPaint.strokeWidth / 2f,
|
||||
borderPaint
|
||||
)
|
||||
|
||||
return BitmapDrawable(context.resources, bitmap)
|
||||
}
|
||||
}
|
||||
@@ -49,6 +49,7 @@ class PreferenceViewModel @Inject constructor(
|
||||
private val autoKeyboardLiveData: MutableLiveData<Boolean> = MutableLiveData()
|
||||
private val lockSettingsLiveData: MutableLiveData<Boolean> = MutableLiveData()
|
||||
private val disableAnimationsLiveData: MutableLiveData<Boolean> = MutableLiveData()
|
||||
private val showNotificationDotsLiveData: MutableLiveData<Boolean> = MutableLiveData()
|
||||
private val appGroupPaddingSizeLiveData: MutableLiveData<Float> = MutableLiveData()
|
||||
private val appPaddingSizeLiveData: MutableLiveData<Float> = MutableLiveData()
|
||||
|
||||
@@ -297,6 +298,11 @@ class PreferenceViewModel @Inject constructor(
|
||||
disableAnimationsLiveData.postValue((preferenceHelper.disableAnimations))
|
||||
}
|
||||
|
||||
fun setShowNotificationDots(showNotificationDots: Boolean) {
|
||||
preferenceHelper.showNotificationDots = showNotificationDots
|
||||
showNotificationDotsLiveData.postValue((preferenceHelper.showNotificationDots))
|
||||
}
|
||||
|
||||
fun setAppLanguage(appLanguage: Constants.Language) {
|
||||
preferenceHelper.appLanguage = appLanguage
|
||||
appLanguageLiveData.postValue((preferenceHelper.appLanguage))
|
||||
|
||||
@@ -238,6 +238,37 @@
|
||||
tools:ignore="TouchTargetSizeCheck" />
|
||||
|
||||
</androidx.appcompat.widget.LinearLayoutCompat>
|
||||
|
||||
<androidx.appcompat.widget.LinearLayoutCompat
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:orientation="horizontal"
|
||||
tools:ignore="MissingConstraints">
|
||||
|
||||
<androidx.appcompat.widget.AppCompatTextView
|
||||
android:id="@+id/showNotificationDots_text"
|
||||
style="@style/TextDefaultStyle"
|
||||
android:layout_width="0dp"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_weight="1"
|
||||
android:gravity="left|center"
|
||||
android:text="@string/settings_display_notification_dots"
|
||||
android:textSize="@dimen/text_large"
|
||||
app:layout_constraintStart_toStartOf="parent"
|
||||
app:layout_constraintTop_toTopOf="parent"
|
||||
tools:ignore="RtlHardcoded" />
|
||||
|
||||
<androidx.appcompat.widget.SwitchCompat
|
||||
android:id="@+id/showNotificationDots_switchCompat"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:scaleX="0.7"
|
||||
android:scaleY="0.8"
|
||||
android:thumb="@drawable/shape_switch_thumb"
|
||||
app:track="@drawable/selector_switch"
|
||||
tools:ignore="TouchTargetSizeCheck" />
|
||||
|
||||
</androidx.appcompat.widget.LinearLayoutCompat>
|
||||
</androidx.appcompat.widget.LinearLayoutCompat>
|
||||
|
||||
<androidx.appcompat.widget.LinearLayoutCompat
|
||||
|
||||
@@ -110,6 +110,8 @@
|
||||
<string name="settings_search_from_start">Search From Start</string>
|
||||
<string name="settings_display_lock_settings">Lock Settings</string>
|
||||
<string name="settings_display_disable_animations">Disable Animations</string>
|
||||
<string name="settings_display_notification_dots">Notification Dots</string>
|
||||
<string name="toast_cannot_open_notification_access_settings">Cannot open notification access settings.</string>
|
||||
|
||||
<string name="settings_appearance_text_size_title">Size</string>
|
||||
<string name="settings_appearance_color_title">Color</string>
|
||||
@@ -200,6 +202,7 @@
|
||||
<string name="accessibility_settings_disable">Disable</string>
|
||||
|
||||
<string name="accessibility_service_name">Easy Launcher Actions Service</string>
|
||||
<string name="notification_badge_service_label">Easy Launcher - notification dots</string>
|
||||
<string name="accessibility_service_desc">Please turn on accessibility service to use double tap to lock feature in Easy Launcher.\n\nThis permission is used only to turn off your screen. Our
|
||||
accessibility service does not collect or share any data.</string>
|
||||
|
||||
|
||||
Reference in New Issue
Block a user