mirror of
https://github.com/Michatec/Radio.git
synced 2026-06-03 12:30:28 +02:00
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:
@@ -1,25 +1,30 @@
|
|||||||
package com.michatec.radio
|
package com.michatec.radio
|
||||||
|
|
||||||
import android.Manifest
|
import android.Manifest
|
||||||
import android.os.Build
|
|
||||||
import android.content.Context
|
import android.content.Context
|
||||||
|
import android.content.Intent
|
||||||
import android.content.SharedPreferences
|
import android.content.SharedPreferences
|
||||||
import android.content.pm.PackageManager
|
import android.content.pm.PackageManager
|
||||||
import android.content.res.Configuration
|
import android.content.res.Configuration
|
||||||
|
import android.net.Uri
|
||||||
|
import android.os.Build
|
||||||
import android.os.Bundle
|
import android.os.Bundle
|
||||||
import android.os.Handler
|
import android.os.Handler
|
||||||
import android.os.Looper
|
import android.os.Looper
|
||||||
|
import android.provider.Settings
|
||||||
import android.view.View
|
import android.view.View
|
||||||
|
import android.widget.FrameLayout
|
||||||
import androidx.activity.enableEdgeToEdge
|
import androidx.activity.enableEdgeToEdge
|
||||||
|
import androidx.activity.result.contract.ActivityResultContracts
|
||||||
import androidx.appcompat.app.AppCompatActivity
|
import androidx.appcompat.app.AppCompatActivity
|
||||||
import androidx.appcompat.widget.Toolbar
|
import androidx.appcompat.widget.Toolbar
|
||||||
|
import androidx.core.app.ActivityCompat
|
||||||
import androidx.core.view.isVisible
|
import androidx.core.view.isVisible
|
||||||
import androidx.navigation.fragment.NavHostFragment
|
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.AppBarConfiguration
|
||||||
import androidx.navigation.ui.NavigationUI
|
import androidx.navigation.ui.NavigationUI
|
||||||
import androidx.navigation.ui.navigateUp
|
import androidx.navigation.ui.navigateUp
|
||||||
|
import com.google.android.material.snackbar.Snackbar
|
||||||
import com.michatec.radio.helpers.AppThemeHelper
|
import com.michatec.radio.helpers.AppThemeHelper
|
||||||
import com.michatec.radio.helpers.FileHelper
|
import com.michatec.radio.helpers.FileHelper
|
||||||
import com.michatec.radio.helpers.LanguageHelper
|
import com.michatec.radio.helpers.LanguageHelper
|
||||||
@@ -45,11 +50,24 @@ class MainActivity : AppCompatActivity() {
|
|||||||
ActivityResultContracts.RequestPermission()
|
ActivityResultContracts.RequestPermission()
|
||||||
) { isGranted ->
|
) { isGranted ->
|
||||||
if (!isGranted) {
|
if (!isGranted) {
|
||||||
Snackbar.make(
|
val snackbar = Snackbar.make(
|
||||||
findViewById(android.R.id.content),
|
findViewById(android.R.id.content),
|
||||||
R.string.snackbar_failed_permission_notification,
|
R.string.snackbar_failed_permission_notification,
|
||||||
Snackbar.LENGTH_LONG
|
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,21 +6,20 @@ import android.app.NotificationManager
|
|||||||
import android.app.PendingIntent
|
import android.app.PendingIntent
|
||||||
import android.content.Context
|
import android.content.Context
|
||||||
import android.content.Intent
|
import android.content.Intent
|
||||||
import com.michatec.radio.R
|
|
||||||
|
|
||||||
object NotificationSys {
|
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 CHANNEL_NAME = "Notifications"
|
||||||
private const val NOTIFICATION_ID = 1001
|
private const val NOTIFICATION_ID = 5000
|
||||||
|
|
||||||
fun createNotificationChannel(context: Context) {
|
fun createNotificationChannel(context: Context) {
|
||||||
val notificationManager = context.getSystemService(Context.NOTIFICATION_SERVICE) as NotificationManager
|
val notificationManager = context.getSystemService(Context.NOTIFICATION_SERVICE) as NotificationManager
|
||||||
|
|
||||||
if (notificationManager.getNotificationChannel(CHANNEL_ID) == null) {
|
if (notificationManager.getNotificationChannel(CHANNEL_ID) == null) {
|
||||||
val channel = NotificationChannel(
|
val channel = NotificationChannel(
|
||||||
CHANNEL_ID,
|
CHANNEL_ID,
|
||||||
CHANNEL_NAME,
|
CHANNEL_NAME,
|
||||||
NotificationManager.IMPORTANCE_DEFAULT
|
NotificationManager.IMPORTANCE_LOW
|
||||||
).apply {
|
).apply {
|
||||||
description = context.getString(R.string.notification_channel_description)
|
description = context.getString(R.string.notification_channel_description)
|
||||||
}
|
}
|
||||||
@@ -37,9 +36,9 @@ object NotificationSys {
|
|||||||
}
|
}
|
||||||
|
|
||||||
val pendingIntent = PendingIntent.getActivity(
|
val pendingIntent = PendingIntent.getActivity(
|
||||||
context,
|
context,
|
||||||
0,
|
0,
|
||||||
targetIntent,
|
targetIntent,
|
||||||
PendingIntent.FLAG_UPDATE_CURRENT or PendingIntent.FLAG_IMMUTABLE
|
PendingIntent.FLAG_UPDATE_CURRENT or PendingIntent.FLAG_IMMUTABLE
|
||||||
)
|
)
|
||||||
|
|
||||||
@@ -54,10 +53,4 @@ object NotificationSys {
|
|||||||
|
|
||||||
notificationManager.notify(NOTIFICATION_ID, notification)
|
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
|
package com.michatec.radio
|
||||||
|
|
||||||
|
import android.Manifest
|
||||||
import android.app.Activity
|
import android.app.Activity
|
||||||
import android.content.ComponentName
|
import android.content.ComponentName
|
||||||
import android.content.Context
|
import android.content.Context
|
||||||
@@ -13,6 +14,7 @@ import android.util.Log
|
|||||||
import android.view.LayoutInflater
|
import android.view.LayoutInflater
|
||||||
import android.view.View
|
import android.view.View
|
||||||
import android.view.ViewGroup
|
import android.view.ViewGroup
|
||||||
|
import android.widget.FrameLayout
|
||||||
import android.widget.Toast
|
import android.widget.Toast
|
||||||
import androidx.activity.OnBackPressedCallback
|
import androidx.activity.OnBackPressedCallback
|
||||||
import androidx.activity.result.ActivityResultLauncher
|
import androidx.activity.result.ActivityResultLauncher
|
||||||
@@ -60,7 +62,6 @@ import com.michatec.radio.extensions.*
|
|||||||
import com.michatec.radio.helpers.*
|
import com.michatec.radio.helpers.*
|
||||||
import com.michatec.radio.ui.LayoutHolder
|
import com.michatec.radio.ui.LayoutHolder
|
||||||
import com.michatec.radio.ui.PlayerState
|
import com.michatec.radio.ui.PlayerState
|
||||||
import com.michatec.radio.BuildConfig
|
|
||||||
import kotlinx.coroutines.*
|
import kotlinx.coroutines.*
|
||||||
import kotlinx.coroutines.Dispatchers.IO
|
import kotlinx.coroutines.Dispatchers.IO
|
||||||
import kotlinx.coroutines.Dispatchers.Main
|
import kotlinx.coroutines.Dispatchers.Main
|
||||||
@@ -101,6 +102,10 @@ class PlayerFragment : Fragment(),
|
|||||||
context?.packageManager?.hasSystemFeature(PackageManager.FEATURE_LEANBACK) == true
|
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 */
|
/* Overrides onCreate from Fragment */
|
||||||
override fun onCreate(savedInstanceState: Bundle?) {
|
override fun onCreate(savedInstanceState: Bundle?) {
|
||||||
@@ -840,7 +845,7 @@ class PlayerFragment : Fragment(),
|
|||||||
if (latestVersion != current && !BuildConfig.IS_DEBUG_ENABLED) {
|
if (latestVersion != current && !BuildConfig.IS_DEBUG_ENABLED) {
|
||||||
// We have an update available, tell our user about it
|
// We have an update available, tell our user about it
|
||||||
view?.let {
|
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) {
|
.setAction(R.string.snackbar_show) {
|
||||||
val releaseurl = getString(R.string.snackbar_url_app_home_page)
|
val releaseurl = getString(R.string.snackbar_url_app_home_page)
|
||||||
val i = Intent(Intent.ACTION_VIEW)
|
val i = Intent(Intent.ACTION_VIEW)
|
||||||
@@ -853,17 +858,19 @@ class PlayerFragment : Fragment(),
|
|||||||
ContextCompat.getColor(
|
ContextCompat.getColor(
|
||||||
requireActivity(),
|
requireActivity(),
|
||||||
R.color.default_neutral_white))
|
R.color.default_neutral_white))
|
||||||
.show()
|
|
||||||
}
|
|
||||||
|
|
||||||
if (!isAndroidTV) {
|
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)
|
val releaseUrl = getString(R.string.snackbar_url_app_home_page)
|
||||||
|
val updateIntent = Intent(Intent.ACTION_VIEW, releaseUrl.toUri()).apply {
|
||||||
// Create the clean browser intent that will trigger ONLY when the notification is tapped
|
|
||||||
val updateIntent = Intent(Intent.ACTION_VIEW, Uri.parse(releaseUrl)).apply {
|
|
||||||
putExtra("SOURCE", "SELF")
|
putExtra("SOURCE", "SELF")
|
||||||
}
|
}
|
||||||
|
|
||||||
NotificationSys.showNotification(
|
NotificationSys.showNotification(
|
||||||
requireContext(),
|
requireContext(),
|
||||||
"${getString(R.string.app_name)} $latestVersion",
|
"${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.ThemeSelectionDialog
|
||||||
import com.michatec.radio.dialogs.YesNoDialog
|
import com.michatec.radio.dialogs.YesNoDialog
|
||||||
import com.michatec.radio.helpers.*
|
import com.michatec.radio.helpers.*
|
||||||
import com.michatec.radio.NotificationSys
|
|
||||||
import android.content.pm.PackageManager
|
import android.content.pm.PackageManager
|
||||||
import kotlinx.coroutines.CoroutineScope
|
import kotlinx.coroutines.CoroutineScope
|
||||||
import kotlinx.coroutines.Dispatchers.IO
|
import kotlinx.coroutines.Dispatchers.IO
|
||||||
@@ -45,6 +44,10 @@ class SettingsFragment : PreferenceFragmentCompat(), YesNoDialog.YesNoDialogList
|
|||||||
context?.packageManager?.hasSystemFeature(PackageManager.FEATURE_LEANBACK) == true
|
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 */
|
/* Overrides onViewCreated from PreferenceFragmentCompat */
|
||||||
override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
|
override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
|
||||||
super.onViewCreated(view, savedInstanceState)
|
super.onViewCreated(view, savedInstanceState)
|
||||||
@@ -386,7 +389,7 @@ class SettingsFragment : PreferenceFragmentCompat(), YesNoDialog.YesNoDialogList
|
|||||||
preferenceCategoryGeneral.addPreference(preferenceThemeSelection)
|
preferenceCategoryGeneral.addPreference(preferenceThemeSelection)
|
||||||
preferenceCategoryGeneral.addPreference(preferenceLanguageSelection)
|
preferenceCategoryGeneral.addPreference(preferenceLanguageSelection)
|
||||||
|
|
||||||
if (!isAndroidTV) {
|
if (!isAndroidTV && isPermissionGranted(activity as Context, android.Manifest.permission.POST_NOTIFICATIONS)) {
|
||||||
preferenceCategoryGeneral.addPreference(preferenceTestNotification)
|
preferenceCategoryGeneral.addPreference(preferenceTestNotification)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -5,6 +5,7 @@
|
|||||||
android:layout_width="match_parent"
|
android:layout_width="match_parent"
|
||||||
android:layout_height="match_parent"
|
android:layout_height="match_parent"
|
||||||
android:background="?android:attr/colorBackground"
|
android:background="?android:attr/colorBackground"
|
||||||
|
android:configChanges="orientation|screenSize|screenLayout|smallestScreenSize"
|
||||||
android:fitsSystemWindows="true"
|
android:fitsSystemWindows="true"
|
||||||
tools:context=".MainActivity">
|
tools:context=".MainActivity">
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user