feat(android): refine notification permissions and UI layout

Add explicit `POST_NOTIFICATIONS` permission checks in `SettingsFragment` and `PlayerFragment` before triggering notifications or showing related preferences.

Update the permission launcher in `MainActivity` to provide a "Settings" action that links to the system application details when the notification permission is permanently denied.

Improve UI and system integration by:
* Adjusting `Snackbar` positioning with bottom margins in `MainActivity` and `PlayerFragment`.
* Updating `NotificationSys` to use a new channel ID with `IMPORTANCE_LOW`.
* Configuring `MainActivity` in `activity_main.xml` to handle orientation and screen size configuration changes manually.
This commit is contained in:
2026-05-31 19:27:51 +02:00
parent c1bc8b3e19
commit 83752c356f
5 changed files with 54 additions and 32 deletions
@@ -1,25 +1,30 @@
package com.michatec.radio
import android.Manifest
import android.os.Build
import android.content.Context
import android.content.Intent
import android.content.SharedPreferences
import android.content.pm.PackageManager
import android.content.res.Configuration
import android.net.Uri
import android.os.Build
import android.os.Bundle
import android.os.Handler
import android.os.Looper
import android.provider.Settings
import android.view.View
import android.widget.FrameLayout
import androidx.activity.enableEdgeToEdge
import androidx.activity.result.contract.ActivityResultContracts
import androidx.appcompat.app.AppCompatActivity
import androidx.appcompat.widget.Toolbar
import androidx.core.app.ActivityCompat
import androidx.core.view.isVisible
import androidx.navigation.fragment.NavHostFragment
import androidx.activity.result.contract.ActivityResultContracts
import com.google.android.material.snackbar.Snackbar
import androidx.navigation.ui.AppBarConfiguration
import androidx.navigation.ui.NavigationUI
import androidx.navigation.ui.navigateUp
import com.google.android.material.snackbar.Snackbar
import com.michatec.radio.helpers.AppThemeHelper
import com.michatec.radio.helpers.FileHelper
import com.michatec.radio.helpers.LanguageHelper
@@ -45,11 +50,24 @@ class MainActivity : AppCompatActivity() {
ActivityResultContracts.RequestPermission()
) { isGranted ->
if (!isGranted) {
Snackbar.make(
val snackbar = Snackbar.make(
findViewById(android.R.id.content),
R.string.snackbar_failed_permission_notification,
Snackbar.LENGTH_LONG
).show()
)
val params = snackbar.view.layoutParams as FrameLayout.LayoutParams
params.bottomMargin = 300
// If the user permanently denied the permission, show a link to settings
if (!ActivityCompat.shouldShowRequestPermissionRationale(this, Manifest.permission.POST_NOTIFICATIONS)) {
snackbar.setAction(R.string.fragment_settings_title) {
val intent = Intent(Settings.ACTION_APPLICATION_DETAILS_SETTINGS).apply {
data = Uri.fromParts("package", packageName, null)
}
startActivity(intent)
}
}
snackbar.view.layoutParams = params
snackbar.show()
}
}
@@ -6,12 +6,11 @@ import android.app.NotificationManager
import android.app.PendingIntent
import android.content.Context
import android.content.Intent
import com.michatec.radio.R
object NotificationSys {
private const val CHANNEL_ID = "com.michatec.radio.channel"
private const val CHANNEL_ID = "com.michatec.radio.channel_messages"
private const val CHANNEL_NAME = "Notifications"
private const val NOTIFICATION_ID = 1001
private const val NOTIFICATION_ID = 5000
fun createNotificationChannel(context: Context) {
val notificationManager = context.getSystemService(Context.NOTIFICATION_SERVICE) as NotificationManager
@@ -20,7 +19,7 @@ object NotificationSys {
val channel = NotificationChannel(
CHANNEL_ID,
CHANNEL_NAME,
NotificationManager.IMPORTANCE_DEFAULT
NotificationManager.IMPORTANCE_LOW
).apply {
description = context.getString(R.string.notification_channel_description)
}
@@ -54,10 +53,4 @@ object NotificationSys {
notificationManager.notify(NOTIFICATION_ID, notification)
}
fun showNotification(context: Context, titleResId: Int, contentResId: Int, intent: Intent? = null) {
val title = context.getString(titleResId)
val content = context.getString(contentResId)
showNotification(context, title, content, intent)
}
}
@@ -1,5 +1,6 @@
package com.michatec.radio
import android.Manifest
import android.app.Activity
import android.content.ComponentName
import android.content.Context
@@ -13,6 +14,7 @@ import android.util.Log
import android.view.LayoutInflater
import android.view.View
import android.view.ViewGroup
import android.widget.FrameLayout
import android.widget.Toast
import androidx.activity.OnBackPressedCallback
import androidx.activity.result.ActivityResultLauncher
@@ -60,7 +62,6 @@ import com.michatec.radio.extensions.*
import com.michatec.radio.helpers.*
import com.michatec.radio.ui.LayoutHolder
import com.michatec.radio.ui.PlayerState
import com.michatec.radio.BuildConfig
import kotlinx.coroutines.*
import kotlinx.coroutines.Dispatchers.IO
import kotlinx.coroutines.Dispatchers.Main
@@ -101,6 +102,10 @@ class PlayerFragment : Fragment(),
context?.packageManager?.hasSystemFeature(PackageManager.FEATURE_LEANBACK) == true
}
private fun isPermissionGranted(context: Context, permission: String): Boolean {
return context.checkSelfPermission(permission) == PackageManager.PERMISSION_GRANTED
}
/* Overrides onCreate from Fragment */
override fun onCreate(savedInstanceState: Bundle?) {
@@ -840,7 +845,7 @@ class PlayerFragment : Fragment(),
if (latestVersion != current && !BuildConfig.IS_DEBUG_ENABLED) {
// We have an update available, tell our user about it
view?.let {
Snackbar.make(it, getString(R.string.app_name) + " " + latestVersion + " " + getString(R.string.snackbar_update_available), 10000)
val snackbar = Snackbar.make(it, getString(R.string.app_name) + " " + latestVersion + " " + getString(R.string.snackbar_update_available), 10000)
.setAction(R.string.snackbar_show) {
val releaseurl = getString(R.string.snackbar_url_app_home_page)
val i = Intent(Intent.ACTION_VIEW)
@@ -853,17 +858,19 @@ class PlayerFragment : Fragment(),
ContextCompat.getColor(
requireActivity(),
R.color.default_neutral_white))
.show()
}
if (!isAndroidTV) {
val params = snackbar.view.layoutParams as FrameLayout.LayoutParams
params.bottomMargin = 300
snackbar.view.layoutParams = params
}
snackbar.show()
}
if (!isAndroidTV && isPermissionGranted(requireContext(), Manifest.permission.POST_NOTIFICATIONS)) {
val releaseUrl = getString(R.string.snackbar_url_app_home_page)
// Create the clean browser intent that will trigger ONLY when the notification is tapped
val updateIntent = Intent(Intent.ACTION_VIEW, Uri.parse(releaseUrl)).apply {
val updateIntent = Intent(Intent.ACTION_VIEW, releaseUrl.toUri()).apply {
putExtra("SOURCE", "SELF")
}
NotificationSys.showNotification(
requireContext(),
"${getString(R.string.app_name)} $latestVersion",
@@ -22,7 +22,6 @@ import com.michatec.radio.dialogs.PresetSelectionDialog
import com.michatec.radio.dialogs.ThemeSelectionDialog
import com.michatec.radio.dialogs.YesNoDialog
import com.michatec.radio.helpers.*
import com.michatec.radio.NotificationSys
import android.content.pm.PackageManager
import kotlinx.coroutines.CoroutineScope
import kotlinx.coroutines.Dispatchers.IO
@@ -45,6 +44,10 @@ class SettingsFragment : PreferenceFragmentCompat(), YesNoDialog.YesNoDialogList
context?.packageManager?.hasSystemFeature(PackageManager.FEATURE_LEANBACK) == true
}
private fun isPermissionGranted(context: Context, permission: String): Boolean {
return context.checkSelfPermission(permission) == PackageManager.PERMISSION_GRANTED
}
/* Overrides onViewCreated from PreferenceFragmentCompat */
override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
super.onViewCreated(view, savedInstanceState)
@@ -386,7 +389,7 @@ class SettingsFragment : PreferenceFragmentCompat(), YesNoDialog.YesNoDialogList
preferenceCategoryGeneral.addPreference(preferenceThemeSelection)
preferenceCategoryGeneral.addPreference(preferenceLanguageSelection)
if (!isAndroidTV) {
if (!isAndroidTV && isPermissionGranted(activity as Context, android.Manifest.permission.POST_NOTIFICATIONS)) {
preferenceCategoryGeneral.addPreference(preferenceTestNotification)
}
@@ -5,6 +5,7 @@
android:layout_width="match_parent"
android:layout_height="match_parent"
android:background="?android:attr/colorBackground"
android:configChanges="orientation|screenSize|screenLayout|smallestScreenSize"
android:fitsSystemWindows="true"
tools:context=".MainActivity">