diff options
| author | Dirk Sierd de Vries <contact@lecapuchon.nl> | 2026-08-09 19:22:55 +0200 |
|---|---|---|
| committer | Dirk Sierd de Vries <contact@lecapuchon.nl> | 2026-08-09 19:50:05 +0200 |
| commit | f9407d9cc8eb2edb4ab6a29a117b3ed11b4b8080 (patch) | |
| tree | 9de2af5f6b43e9e0f40b2d6eede4f19a304f5c4c /app | |
| parent | 7971690ac82fc5a0f3e547a8c2f3f1acfa5608ab (diff) | |
| download | SidePhOnelauncher-f9407d9cc8eb2edb4ab6a29a117b3ed11b4b8080.tar.gz SidePhOnelauncher-f9407d9cc8eb2edb4ab6a29a117b3ed11b4b8080.tar.bz2 SidePhOnelauncher-f9407d9cc8eb2edb4ab6a29a117b3ed11b4b8080.zip | |
Add subtle notification dots to home app buttons
Diffstat (limited to 'app')
6 files changed, 215 insertions, 39 deletions
diff --git a/app/src/main/AndroidManifest.xml b/app/src/main/AndroidManifest.xml index 91aa7af..d90ae12 100644 --- a/app/src/main/AndroidManifest.xml +++ b/app/src/main/AndroidManifest.xml @@ -102,6 +102,16 @@ </intent-filter> </service> + <service + android:name=".helper.HomeNotificationListenerService" + android:exported="false" + android:label="@string/app_name" + android:permission="android.permission.BIND_NOTIFICATION_LISTENER_SERVICE"> + <intent-filter> + <action android:name="android.service.notification.NotificationListenerService" /> + </intent-filter> + </service> + </application> </manifest>
\ No newline at end of file diff --git a/app/src/main/java/app/olauncher/helper/HomeNotificationListenerService.kt b/app/src/main/java/app/olauncher/helper/HomeNotificationListenerService.kt new file mode 100644 index 0000000..59facf8 --- /dev/null +++ b/app/src/main/java/app/olauncher/helper/HomeNotificationListenerService.kt @@ -0,0 +1,27 @@ +package app.olauncher.helper + +import android.service.notification.NotificationListenerService +import android.service.notification.StatusBarNotification + +class HomeNotificationListenerService : NotificationListenerService() { + + override fun onListenerConnected() { + super.onListenerConnected() + NotificationDotRepository.update(activeNotifications) + } + + override fun onNotificationPosted(sbn: StatusBarNotification?) { + super.onNotificationPosted(sbn) + NotificationDotRepository.update(activeNotifications) + } + + override fun onNotificationRemoved(sbn: StatusBarNotification?) { + super.onNotificationRemoved(sbn) + NotificationDotRepository.update(activeNotifications) + } + + override fun onListenerDisconnected() { + NotificationDotRepository.clear() + super.onListenerDisconnected() + } +} diff --git a/app/src/main/java/app/olauncher/helper/NotificationDotRepository.kt b/app/src/main/java/app/olauncher/helper/NotificationDotRepository.kt new file mode 100644 index 0000000..0040ea5 --- /dev/null +++ b/app/src/main/java/app/olauncher/helper/NotificationDotRepository.kt @@ -0,0 +1,58 @@ +package app.olauncher.helper + +import android.app.Notification +import android.service.notification.StatusBarNotification +import java.util.concurrent.CopyOnWriteArraySet + +object NotificationDotRepository { + private val listeners = CopyOnWriteArraySet<() -> Unit>() + + @Volatile + private var packagesWithNotifications: Set<String> = emptySet() + + fun addListener(listener: () -> Unit) { + listeners += listener + } + + fun removeListener(listener: () -> Unit) { + listeners -= listener + } + + fun hasActiveNotification(packageName: String): Boolean { + if (packageName.isBlank()) return false + return packageName in packagesWithNotifications + } + + fun update(activeNotifications: Array<StatusBarNotification>?) { + val nextPackages = activeNotifications + ?.asSequence() + ?.filterNot(::shouldIgnore) + ?.map { it.packageName } + ?.filter { it.isNotBlank() } + ?.toSet() + ?: emptySet() + + if (nextPackages == packagesWithNotifications) return + packagesWithNotifications = nextPackages + listeners.forEach { it() } + } + + fun clear() { + if (packagesWithNotifications.isEmpty()) return + packagesWithNotifications = emptySet() + listeners.forEach { it() } + } + + private fun shouldIgnore(sbn: StatusBarNotification): Boolean { + val notification = sbn.notification + val flags = notification.flags + val category = notification.category + val isGroupSummary = flags and Notification.FLAG_GROUP_SUMMARY != 0 + val isForegroundService = flags and Notification.FLAG_FOREGROUND_SERVICE != 0 + val isOngoing = flags and Notification.FLAG_ONGOING_EVENT != 0 + val isPersistent = !sbn.isClearable + val isTransport = category == Notification.CATEGORY_TRANSPORT + val isService = category == Notification.CATEGORY_SERVICE + return isGroupSummary || isForegroundService || isOngoing || isPersistent || isTransport || isService + } +} diff --git a/app/src/main/java/app/olauncher/ui/HomeAppTextView.kt b/app/src/main/java/app/olauncher/ui/HomeAppTextView.kt new file mode 100644 index 0000000..144b6b9 --- /dev/null +++ b/app/src/main/java/app/olauncher/ui/HomeAppTextView.kt @@ -0,0 +1,51 @@ +package app.olauncher.ui + +import android.content.Context +import android.graphics.Canvas +import android.graphics.Color +import android.graphics.Paint +import android.util.AttributeSet +import android.widget.TextView +import app.olauncher.helper.dpToPx + +class HomeAppTextView @JvmOverloads constructor( + context: Context, + attrs: AttributeSet? = null, + defStyleAttr: Int = android.R.attr.textViewStyle, +) : TextView(context, attrs, defStyleAttr) { + + private val dotPaint = Paint(Paint.ANTI_ALIAS_FLAG).apply { + color = Color.WHITE + alpha = 220 + style = Paint.Style.FILL + } + + private val dotRadiusPx = 3.dpToPx().toFloat() + private val reservedSideSpacePx = 14.dpToPx() + + private var showNotificationDot = false + + init { + setPaddingRelative( + paddingStart + reservedSideSpacePx, + paddingTop, + paddingEnd + reservedSideSpacePx, + paddingBottom, + ) + } + + fun setShowNotificationDot(show: Boolean) { + if (showNotificationDot == show) return + showNotificationDot = show + invalidate() + } + + override fun onDraw(canvas: Canvas) { + super.onDraw(canvas) + if (!showNotificationDot) return + + val cx = width - reservedSideSpacePx / 2f + val cy = height / 2f + canvas.drawCircle(cx, cy, dotRadiusPx, dotPaint) + } +} diff --git a/app/src/main/java/app/olauncher/ui/HomeFragment.kt b/app/src/main/java/app/olauncher/ui/HomeFragment.kt index 535002c..1d14c61 100644 --- a/app/src/main/java/app/olauncher/ui/HomeFragment.kt +++ b/app/src/main/java/app/olauncher/ui/HomeFragment.kt @@ -32,6 +32,7 @@ import app.olauncher.data.AppModel import app.olauncher.data.Constants import app.olauncher.data.Prefs import app.olauncher.databinding.FragmentHomeBinding +import app.olauncher.helper.NotificationDotRepository import app.olauncher.helper.appUsagePermissionGranted import app.olauncher.helper.dpToPx import app.olauncher.helper.expandNotificationDrawer @@ -62,6 +63,11 @@ class HomeFragment : Fragment(), View.OnClickListener, View.OnLongClickListener private var dpadLeftKeyDownTime = 0L private var dpadRightKeyDownTime = 0L + private val notificationDotListener: () -> Unit = { + activity?.runOnUiThread { + if (_binding != null) updateHomeNotificationDots() + } + } override fun onCreateView(inflater: LayoutInflater, container: ViewGroup?, savedInstanceState: Bundle?): View { _binding = FragmentHomeBinding.inflate(inflater, container, false) @@ -86,6 +92,7 @@ class HomeFragment : Fragment(), View.OnClickListener, View.OnLongClickListener override fun onResume() { super.onResume() + NotificationDotRepository.addListener(notificationDotListener) dpadLeftKeyDownTime = 0L dpadRightKeyDownTime = 0L populateHomeScreen(false) @@ -386,14 +393,7 @@ class HomeFragment : Fragment(), View.OnClickListener, View.OnLongClickListener val verticalGravity = if (prefs.homeBottomAlignment) Gravity.BOTTOM else Gravity.CENTER_VERTICAL binding.homeAppsLayout.gravity = horizontalGravity or verticalGravity binding.dateTimeLayout.gravity = horizontalGravity - binding.homeApp1.gravity = horizontalGravity - binding.homeApp2.gravity = horizontalGravity - binding.homeApp3.gravity = horizontalGravity - binding.homeApp4.gravity = horizontalGravity - binding.homeApp5.gravity = horizontalGravity - binding.homeApp6.gravity = horizontalGravity - binding.homeApp7.gravity = horizontalGravity - binding.homeApp8.gravity = horizontalGravity + homeAppTextViews().forEach { it.gravity = horizontalGravity } } private fun populateDateTime() { @@ -452,56 +452,56 @@ class HomeFragment : Fragment(), View.OnClickListener, View.OnLongClickListener if (homeAppsNum == 0) return binding.homeApp1.visibility = View.VISIBLE - if (!setHomeAppText(binding.homeApp1, prefs.appName1, prefs.appPackage1, prefs.appUser1, prefs.isShortcut1, prefs.shortcutId1)) { + if (!setHomeAppText(binding.homeApp1 as TextView, prefs.appName1, prefs.appPackage1, prefs.appUser1, prefs.isShortcut1, prefs.shortcutId1)) { prefs.appName1 = "" prefs.appPackage1 = "" } if (homeAppsNum == 1) return binding.homeApp2.visibility = View.VISIBLE - if (!setHomeAppText(binding.homeApp2, prefs.appName2, prefs.appPackage2, prefs.appUser2, prefs.isShortcut2, prefs.shortcutId2)) { + if (!setHomeAppText(binding.homeApp2 as TextView, prefs.appName2, prefs.appPackage2, prefs.appUser2, prefs.isShortcut2, prefs.shortcutId2)) { prefs.appName2 = "" prefs.appPackage2 = "" } if (homeAppsNum == 2) return binding.homeApp3.visibility = View.VISIBLE - if (!setHomeAppText(binding.homeApp3, prefs.appName3, prefs.appPackage3, prefs.appUser3, prefs.isShortcut3, prefs.shortcutId3)) { + if (!setHomeAppText(binding.homeApp3 as TextView, prefs.appName3, prefs.appPackage3, prefs.appUser3, prefs.isShortcut3, prefs.shortcutId3)) { prefs.appName3 = "" prefs.appPackage3 = "" } if (homeAppsNum == 3) return binding.homeApp4.visibility = View.VISIBLE - if (!setHomeAppText(binding.homeApp4, prefs.appName4, prefs.appPackage4, prefs.appUser4, prefs.isShortcut4, prefs.shortcutId4)) { + if (!setHomeAppText(binding.homeApp4 as TextView, prefs.appName4, prefs.appPackage4, prefs.appUser4, prefs.isShortcut4, prefs.shortcutId4)) { prefs.appName4 = "" prefs.appPackage4 = "" } if (homeAppsNum == 4) return binding.homeApp5.visibility = View.VISIBLE - if (!setHomeAppText(binding.homeApp5, prefs.appName5, prefs.appPackage5, prefs.appUser5, prefs.isShortcut5, prefs.shortcutId5)) { + if (!setHomeAppText(binding.homeApp5 as TextView, prefs.appName5, prefs.appPackage5, prefs.appUser5, prefs.isShortcut5, prefs.shortcutId5)) { prefs.appName5 = "" prefs.appPackage5 = "" } if (homeAppsNum == 5) return binding.homeApp6.visibility = View.VISIBLE - if (!setHomeAppText(binding.homeApp6, prefs.appName6, prefs.appPackage6, prefs.appUser6, prefs.isShortcut6, prefs.shortcutId6)) { + if (!setHomeAppText(binding.homeApp6 as TextView, prefs.appName6, prefs.appPackage6, prefs.appUser6, prefs.isShortcut6, prefs.shortcutId6)) { prefs.appName6 = "" prefs.appPackage6 = "" } if (homeAppsNum == 6) return binding.homeApp7.visibility = View.VISIBLE - if (!setHomeAppText(binding.homeApp7, prefs.appName7, prefs.appPackage7, prefs.appUser7, prefs.isShortcut7, prefs.shortcutId7)) { + if (!setHomeAppText(binding.homeApp7 as TextView, prefs.appName7, prefs.appPackage7, prefs.appUser7, prefs.isShortcut7, prefs.shortcutId7)) { prefs.appName7 = "" prefs.appPackage7 = "" } if (homeAppsNum == 7) return binding.homeApp8.visibility = View.VISIBLE - if (!setHomeAppText(binding.homeApp8, prefs.appName8, prefs.appPackage8, prefs.appUser8, prefs.isShortcut8, prefs.shortcutId8)) { + if (!setHomeAppText(binding.homeApp8 as TextView, prefs.appName8, prefs.appPackage8, prefs.appUser8, prefs.isShortcut8, prefs.shortcutId8)) { prefs.appName8 = "" prefs.appPackage8 = "" } @@ -528,25 +528,60 @@ class HomeFragment : Fragment(), View.OnClickListener, View.OnLongClickListener val shortcuts = launcherApps.getShortcuts(query, userHandle) if (shortcuts?.any { it.id == shortcutId } == true) { textView.text = appName + updateHomeAppDot(textView, packageName) return true } textView.text = "" + updateHomeAppDot(textView, null) return false } catch (e: Exception) { e.printStackTrace() textView.text = "" + updateHomeAppDot(textView, null) return false } } if (isPackageInstalled(requireContext(), packageName, userString)) { textView.text = appName + updateHomeAppDot(textView, packageName) return true } textView.text = "" + updateHomeAppDot(textView, null) return false } + private fun updateHomeNotificationDots() { + updateHomeAppDot(binding.homeApp1 as TextView, prefs.appPackage1) + updateHomeAppDot(binding.homeApp2 as TextView, prefs.appPackage2) + updateHomeAppDot(binding.homeApp3 as TextView, prefs.appPackage3) + updateHomeAppDot(binding.homeApp4 as TextView, prefs.appPackage4) + updateHomeAppDot(binding.homeApp5 as TextView, prefs.appPackage5) + updateHomeAppDot(binding.homeApp6 as TextView, prefs.appPackage6) + updateHomeAppDot(binding.homeApp7 as TextView, prefs.appPackage7) + updateHomeAppDot(binding.homeApp8 as TextView, prefs.appPackage8) + } + + private fun homeAppTextViews(): List<TextView> { + return listOf( + binding.homeApp1 as TextView, + binding.homeApp2 as TextView, + binding.homeApp3 as TextView, + binding.homeApp4 as TextView, + binding.homeApp5 as TextView, + binding.homeApp6 as TextView, + binding.homeApp7 as TextView, + binding.homeApp8 as TextView, + ) + } + + private fun updateHomeAppDot(textView: TextView, packageName: String?) { + (textView as? HomeAppTextView)?.setShowNotificationDot( + packageName.isNullOrBlank().not() && NotificationDotRepository.hasActiveNotification(packageName) + ) + } + private fun restoreHomeAppFocusability() { listOf( binding.homeApp1, @@ -867,6 +902,11 @@ class HomeFragment : Fragment(), View.OnClickListener, View.OnLongClickListener } } + override fun onPause() { + NotificationDotRepository.removeListener(notificationDotListener) + super.onPause() + } + override fun onDestroyView() { super.onDestroyView() _binding = null diff --git a/app/src/main/res/layout/fragment_home.xml b/app/src/main/res/layout/fragment_home.xml index 7de8a4a..0f474c2 100644 --- a/app/src/main/res/layout/fragment_home.xml +++ b/app/src/main/res/layout/fragment_home.xml @@ -8,22 +8,12 @@ android:orientation="vertical" tools:context=".ui.HomeFragment"> - <!-- Placeholder layout for locking screen--> <FrameLayout android:id="@+id/lock" android:layout_width="match_parent" android:layout_height="1dp" android:contentDescription="@string/lock_layout_description" /> - <!-- Home button for recents feature disabled --> - <!-- Placeholder layout for showing recent apps - <FrameLayout - android:id="@+id/recents" - android:layout_width="match_parent" - android:layout_height="1dp" - android:contentDescription="@string/recents_layout_description" /> --> - - <!-- Clock and calendar--> <LinearLayout android:id="@+id/dateTimeLayout" android:layout_width="match_parent" @@ -41,6 +31,8 @@ android:layout_height="wrap_content" android:fontFamily="sans-serif-light" android:format12Hour="h:mm" + android:paddingStart="14dp" + android:paddingEnd="14dp" android:textSize="@dimen/time_size" tools:text="02:34" /> @@ -51,12 +43,12 @@ android:layout_height="wrap_content" android:format12Hour="EEE, dd MMM" android:format24Hour="EEE, dd MMM" - android:paddingHorizontal="3dp" + android:paddingStart="7dp" + android:paddingEnd="7dp" android:textSize="@dimen/date_size" tools:text="Thu, 30 Dec" /> </LinearLayout> - <!-- Screen time--> <androidx.appcompat.widget.AppCompatTextView android:id="@+id/tvScreenTime" style="@style/TextSmall" @@ -72,7 +64,6 @@ tools:text="2h 11m" tools:visibility="visible" /> - <!-- Home apps--> <LinearLayout android:id="@+id/homeAppsLayout" android:layout_width="match_parent" @@ -83,7 +74,7 @@ android:paddingTop="112dp" android:paddingBottom="48dp"> - <TextView + <app.olauncher.ui.HomeAppTextView android:id="@+id/homeApp1" style="@style/TextLarge" android:layout_width="wrap_content" @@ -96,7 +87,7 @@ android:visibility="gone" tools:visibility="visible" /> - <TextView + <app.olauncher.ui.HomeAppTextView android:id="@+id/homeApp2" style="@style/TextLarge" android:layout_width="wrap_content" @@ -109,7 +100,7 @@ android:visibility="gone" tools:visibility="visible" /> - <TextView + <app.olauncher.ui.HomeAppTextView android:id="@+id/homeApp3" style="@style/TextLarge" android:layout_width="wrap_content" @@ -122,7 +113,7 @@ android:visibility="gone" tools:visibility="visible" /> - <TextView + <app.olauncher.ui.HomeAppTextView android:id="@+id/homeApp4" style="@style/TextLarge" android:layout_width="wrap_content" @@ -135,7 +126,7 @@ android:visibility="gone" tools:visibility="visible" /> - <TextView + <app.olauncher.ui.HomeAppTextView android:id="@+id/homeApp5" style="@style/TextLarge" android:layout_width="wrap_content" @@ -148,7 +139,7 @@ android:visibility="gone" tools:visibility="visible" /> - <TextView + <app.olauncher.ui.HomeAppTextView android:id="@+id/homeApp6" style="@style/TextLarge" android:layout_width="wrap_content" @@ -161,7 +152,7 @@ android:visibility="gone" tools:visibility="visible" /> - <TextView + <app.olauncher.ui.HomeAppTextView android:id="@+id/homeApp7" style="@style/TextLarge" android:layout_width="wrap_content" @@ -174,7 +165,7 @@ android:visibility="gone" tools:visibility="visible" /> - <TextView + <app.olauncher.ui.HomeAppTextView android:id="@+id/homeApp8" style="@style/TextLarge" android:layout_width="wrap_content" @@ -202,7 +193,6 @@ android:visibility="gone" tools:visibility="visible" /> - <!-- Set default launcher --> <TextView android:id="@+id/setDefaultLauncher" style="@style/TextMedium" @@ -215,4 +205,4 @@ android:maxLines="1" android:text="@string/set_as_default_launcher_u" android:visibility="gone" /> -</FrameLayout>
\ No newline at end of file +</FrameLayout> |
