Merge pull request #278 from cyb3rko/notification-channels
Separate Notification channels for each app
This commit is contained in:
@@ -1,56 +1,93 @@
|
|||||||
package com.github.gotify
|
package com.github.gotify
|
||||||
|
|
||||||
import android.app.NotificationChannel
|
import android.app.NotificationChannel
|
||||||
|
import android.app.NotificationChannelGroup
|
||||||
import android.app.NotificationManager
|
import android.app.NotificationManager
|
||||||
|
import android.app.Service
|
||||||
|
import android.content.Context
|
||||||
import android.graphics.Color
|
import android.graphics.Color
|
||||||
import android.os.Build
|
import android.os.Build
|
||||||
import androidx.annotation.RequiresApi
|
import androidx.annotation.RequiresApi
|
||||||
|
import androidx.preference.PreferenceManager
|
||||||
|
import com.github.gotify.client.model.Application
|
||||||
import com.github.gotify.log.Log
|
import com.github.gotify.log.Log
|
||||||
|
|
||||||
internal object NotificationSupport {
|
internal object NotificationSupport {
|
||||||
@RequiresApi(Build.VERSION_CODES.O)
|
@RequiresApi(Build.VERSION_CODES.O)
|
||||||
fun createChannels(notificationManager: NotificationManager) {
|
fun createForegroundChannel(context: Context, notificationManager: NotificationManager) {
|
||||||
try {
|
// Low importance so that persistent notification can be sorted towards bottom of
|
||||||
// Low importance so that persistent notification can be sorted towards bottom of
|
// notification shade. Also prevents vibrations caused by persistent notification
|
||||||
// notification shade. Also prevents vibrations caused by persistent notification
|
val foreground = NotificationChannel(
|
||||||
val foreground = NotificationChannel(
|
Channel.FOREGROUND,
|
||||||
Channel.FOREGROUND,
|
context.getString(R.string.notification_channel_title_foreground),
|
||||||
"Gotify foreground notification",
|
NotificationManager.IMPORTANCE_LOW
|
||||||
NotificationManager.IMPORTANCE_LOW
|
).apply {
|
||||||
)
|
setShowBadge(false)
|
||||||
foreground.setShowBadge(false)
|
}
|
||||||
|
notificationManager.createNotificationChannel(foreground)
|
||||||
|
}
|
||||||
|
|
||||||
|
@RequiresApi(Build.VERSION_CODES.O)
|
||||||
|
fun createChannels(
|
||||||
|
context: Context,
|
||||||
|
notificationManager: NotificationManager,
|
||||||
|
applications: List<Application>
|
||||||
|
) {
|
||||||
|
if (areAppChannelsRequested(context)) {
|
||||||
|
notificationManager.notificationChannels.forEach { channel ->
|
||||||
|
if (channel.id != Channel.FOREGROUND) {
|
||||||
|
notificationManager.deleteNotificationChannel(channel.id)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
applications.forEach { app ->
|
||||||
|
createAppChannels(context, notificationManager, app.id.toString(), app.name)
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
notificationManager.notificationChannelGroups.forEach { group ->
|
||||||
|
notificationManager.deleteNotificationChannelGroup(group.id)
|
||||||
|
}
|
||||||
|
createGeneralChannels(context, notificationManager)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@RequiresApi(Build.VERSION_CODES.O)
|
||||||
|
private fun createGeneralChannels(
|
||||||
|
context: Context,
|
||||||
|
notificationManager: NotificationManager
|
||||||
|
) {
|
||||||
|
try {
|
||||||
val messagesImportanceMin = NotificationChannel(
|
val messagesImportanceMin = NotificationChannel(
|
||||||
Channel.MESSAGES_IMPORTANCE_MIN,
|
Channel.MESSAGES_IMPORTANCE_MIN,
|
||||||
"Min priority messages (<1)",
|
context.getString(R.string.notification_channel_title_min),
|
||||||
NotificationManager.IMPORTANCE_MIN
|
NotificationManager.IMPORTANCE_MIN
|
||||||
)
|
)
|
||||||
|
|
||||||
val messagesImportanceLow = NotificationChannel(
|
val messagesImportanceLow = NotificationChannel(
|
||||||
Channel.MESSAGES_IMPORTANCE_LOW,
|
Channel.MESSAGES_IMPORTANCE_LOW,
|
||||||
"Low priority messages (1-3)",
|
context.getString(R.string.notification_channel_title_low),
|
||||||
NotificationManager.IMPORTANCE_LOW
|
NotificationManager.IMPORTANCE_LOW
|
||||||
)
|
)
|
||||||
|
|
||||||
val messagesImportanceDefault = NotificationChannel(
|
val messagesImportanceDefault = NotificationChannel(
|
||||||
Channel.MESSAGES_IMPORTANCE_DEFAULT,
|
Channel.MESSAGES_IMPORTANCE_DEFAULT,
|
||||||
"Normal priority messages (4-7)",
|
context.getString(R.string.notification_channel_title_normal),
|
||||||
NotificationManager.IMPORTANCE_DEFAULT
|
NotificationManager.IMPORTANCE_DEFAULT
|
||||||
)
|
).apply {
|
||||||
messagesImportanceDefault.enableLights(true)
|
enableLights(true)
|
||||||
messagesImportanceDefault.lightColor = Color.CYAN
|
lightColor = Color.CYAN
|
||||||
messagesImportanceDefault.enableVibration(true)
|
enableVibration(true)
|
||||||
|
}
|
||||||
|
|
||||||
val messagesImportanceHigh = NotificationChannel(
|
val messagesImportanceHigh = NotificationChannel(
|
||||||
Channel.MESSAGES_IMPORTANCE_HIGH,
|
Channel.MESSAGES_IMPORTANCE_HIGH,
|
||||||
"High priority messages (>7)",
|
context.getString(R.string.notification_channel_title_high),
|
||||||
NotificationManager.IMPORTANCE_HIGH
|
NotificationManager.IMPORTANCE_HIGH
|
||||||
)
|
).apply {
|
||||||
messagesImportanceHigh.enableLights(true)
|
enableLights(true)
|
||||||
messagesImportanceHigh.lightColor = Color.CYAN
|
lightColor = Color.CYAN
|
||||||
messagesImportanceHigh.enableVibration(true)
|
enableVibration(true)
|
||||||
|
}
|
||||||
|
|
||||||
notificationManager.createNotificationChannel(foreground)
|
|
||||||
notificationManager.createNotificationChannel(messagesImportanceMin)
|
notificationManager.createNotificationChannel(messagesImportanceMin)
|
||||||
notificationManager.createNotificationChannel(messagesImportanceLow)
|
notificationManager.createNotificationChannel(messagesImportanceLow)
|
||||||
notificationManager.createNotificationChannel(messagesImportanceDefault)
|
notificationManager.createNotificationChannel(messagesImportanceDefault)
|
||||||
@@ -60,6 +97,88 @@ internal object NotificationSupport {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@RequiresApi(api = Build.VERSION_CODES.O)
|
||||||
|
fun createChannelIfNonexistent(
|
||||||
|
context: Context,
|
||||||
|
groupId: String,
|
||||||
|
channelId: String
|
||||||
|
) {
|
||||||
|
if (!doesNotificationChannelExist(context, channelId)) {
|
||||||
|
val notificationManager = (context as Service)
|
||||||
|
.getSystemService(Context.NOTIFICATION_SERVICE) as NotificationManager
|
||||||
|
createAppChannels(context, notificationManager, groupId, groupId)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@RequiresApi(Build.VERSION_CODES.O)
|
||||||
|
private fun createAppChannels(
|
||||||
|
context: Context,
|
||||||
|
notificationManager: NotificationManager,
|
||||||
|
groupId: String,
|
||||||
|
groupName: String
|
||||||
|
) {
|
||||||
|
try {
|
||||||
|
notificationManager.createNotificationChannelGroup(
|
||||||
|
NotificationChannelGroup(
|
||||||
|
groupId,
|
||||||
|
groupName
|
||||||
|
)
|
||||||
|
)
|
||||||
|
|
||||||
|
val messagesImportanceMin = NotificationChannel(
|
||||||
|
getChannelID(Channel.MESSAGES_IMPORTANCE_MIN, groupId),
|
||||||
|
context.getString(R.string.notification_channel_title_min),
|
||||||
|
NotificationManager.IMPORTANCE_MIN
|
||||||
|
).apply {
|
||||||
|
group = groupId
|
||||||
|
}
|
||||||
|
|
||||||
|
val messagesImportanceLow = NotificationChannel(
|
||||||
|
getChannelID(Channel.MESSAGES_IMPORTANCE_LOW, groupId),
|
||||||
|
context.getString(R.string.notification_channel_title_low),
|
||||||
|
NotificationManager.IMPORTANCE_LOW
|
||||||
|
).apply {
|
||||||
|
group = groupId
|
||||||
|
}
|
||||||
|
|
||||||
|
val messagesImportanceDefault = NotificationChannel(
|
||||||
|
getChannelID(Channel.MESSAGES_IMPORTANCE_DEFAULT, groupId),
|
||||||
|
context.getString(R.string.notification_channel_title_normal),
|
||||||
|
NotificationManager.IMPORTANCE_DEFAULT
|
||||||
|
).apply {
|
||||||
|
enableLights(true)
|
||||||
|
lightColor = Color.CYAN
|
||||||
|
enableVibration(true)
|
||||||
|
group = groupId
|
||||||
|
}
|
||||||
|
|
||||||
|
val messagesImportanceHigh = NotificationChannel(
|
||||||
|
getChannelID(Channel.MESSAGES_IMPORTANCE_HIGH, groupId),
|
||||||
|
context.getString(R.string.notification_channel_title_high),
|
||||||
|
NotificationManager.IMPORTANCE_HIGH
|
||||||
|
).apply {
|
||||||
|
enableLights(true)
|
||||||
|
lightColor = Color.CYAN
|
||||||
|
enableVibration(true)
|
||||||
|
group = groupId
|
||||||
|
}
|
||||||
|
|
||||||
|
notificationManager.createNotificationChannel(messagesImportanceMin)
|
||||||
|
notificationManager.createNotificationChannel(messagesImportanceLow)
|
||||||
|
notificationManager.createNotificationChannel(messagesImportanceDefault)
|
||||||
|
notificationManager.createNotificationChannel(messagesImportanceHigh)
|
||||||
|
} catch (e: Exception) {
|
||||||
|
Log.e("Could not create channel", e)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@RequiresApi(api = Build.VERSION_CODES.O)
|
||||||
|
private fun doesNotificationChannelExist(context: Context, channelId: String): Boolean {
|
||||||
|
val manager = context.getSystemService(Context.NOTIFICATION_SERVICE) as NotificationManager
|
||||||
|
val channel = manager.getNotificationChannel(channelId)
|
||||||
|
return channel != null
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Map {@link com.github.gotify.client.model.Message#getPriority() Gotify message priorities to
|
* Map {@link com.github.gotify.client.model.Message#getPriority() Gotify message priorities to
|
||||||
* Android channels.
|
* Android channels.
|
||||||
@@ -87,6 +206,21 @@ internal object NotificationSupport {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private fun getChannelID(importance: String, groupId: String): String {
|
||||||
|
return "$groupId::$importance"
|
||||||
|
}
|
||||||
|
|
||||||
|
fun getChannelID(priority: Long, groupId: String): String {
|
||||||
|
return getChannelID(convertPriorityToChannel(priority), groupId)
|
||||||
|
}
|
||||||
|
|
||||||
|
fun areAppChannelsRequested(context: Context): Boolean {
|
||||||
|
return PreferenceManager.getDefaultSharedPreferences(context).getBoolean(
|
||||||
|
context.getString(R.string.setting_key_notification_channels),
|
||||||
|
context.resources.getBoolean(R.bool.notification_channels)
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
object Group {
|
object Group {
|
||||||
const val MESSAGES = "GOTIFY_GROUP_MESSAGES"
|
const val MESSAGES = "GOTIFY_GROUP_MESSAGES"
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -2,6 +2,7 @@ package com.github.gotify.init
|
|||||||
|
|
||||||
import android.Manifest
|
import android.Manifest
|
||||||
import android.app.NotificationManager
|
import android.app.NotificationManager
|
||||||
|
import android.content.Context
|
||||||
import android.content.Intent
|
import android.content.Intent
|
||||||
import android.os.Build
|
import android.os.Build
|
||||||
import android.os.Bundle
|
import android.os.Bundle
|
||||||
@@ -41,8 +42,9 @@ internal class InitializationActivity : AppCompatActivity() {
|
|||||||
ThemeHelper.setTheme(this, theme)
|
ThemeHelper.setTheme(this, theme)
|
||||||
|
|
||||||
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {
|
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {
|
||||||
NotificationSupport.createChannels(
|
NotificationSupport.createForegroundChannel(
|
||||||
this.getSystemService(NOTIFICATION_SERVICE) as NotificationManager
|
this,
|
||||||
|
(this.getSystemService(Context.NOTIFICATION_SERVICE) as NotificationManager)
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
UncaughtExceptionHandler.registerCurrentThread()
|
UncaughtExceptionHandler.registerCurrentThread()
|
||||||
|
|||||||
@@ -9,7 +9,7 @@ internal object MessageImageCombiner {
|
|||||||
return messages.map { MessageWithImage(message = it, image = appIdToImage[it.appid]) }
|
return messages.map { MessageWithImage(message = it, image = appIdToImage[it.appid]) }
|
||||||
}
|
}
|
||||||
|
|
||||||
fun appIdToImage(applications: List<Application>): Map<Long, String> {
|
private fun appIdToImage(applications: List<Application>): Map<Long, String> {
|
||||||
val map = mutableMapOf<Long, String>()
|
val map = mutableMapOf<Long, String>()
|
||||||
applications.forEach {
|
applications.forEach {
|
||||||
map[it.id] = it.image
|
map[it.id] = it.image
|
||||||
|
|||||||
@@ -6,19 +6,15 @@ import android.graphics.BitmapFactory
|
|||||||
import com.github.gotify.R
|
import com.github.gotify.R
|
||||||
import com.github.gotify.Settings
|
import com.github.gotify.Settings
|
||||||
import com.github.gotify.Utils
|
import com.github.gotify.Utils
|
||||||
import com.github.gotify.api.Callback
|
|
||||||
import com.github.gotify.api.CertUtils
|
import com.github.gotify.api.CertUtils
|
||||||
import com.github.gotify.api.ClientFactory
|
import com.github.gotify.client.model.Application
|
||||||
import com.github.gotify.client.api.ApplicationApi
|
|
||||||
import com.github.gotify.log.Log
|
import com.github.gotify.log.Log
|
||||||
import com.github.gotify.messages.provider.MessageImageCombiner
|
|
||||||
import com.squareup.picasso.OkHttp3Downloader
|
import com.squareup.picasso.OkHttp3Downloader
|
||||||
import com.squareup.picasso.Picasso
|
import com.squareup.picasso.Picasso
|
||||||
import okhttp3.Cache
|
import okhttp3.Cache
|
||||||
import okhttp3.OkHttpClient
|
import okhttp3.OkHttpClient
|
||||||
import java.io.File
|
import java.io.File
|
||||||
import java.io.IOException
|
import java.io.IOException
|
||||||
import java.util.concurrent.ConcurrentHashMap
|
|
||||||
|
|
||||||
internal class PicassoHandler(private val context: Context, private val settings: Settings) {
|
internal class PicassoHandler(private val context: Context, private val settings: Settings) {
|
||||||
companion object {
|
companion object {
|
||||||
@@ -32,7 +28,6 @@ internal class PicassoHandler(private val context: Context, private val settings
|
|||||||
)
|
)
|
||||||
|
|
||||||
private val picasso = makePicasso()
|
private val picasso = makePicasso()
|
||||||
private val appIdToAppImage = ConcurrentHashMap<Long, String>()
|
|
||||||
|
|
||||||
private fun makePicasso(): Picasso {
|
private fun makePicasso(): Picasso {
|
||||||
val builder = OkHttpClient.Builder()
|
val builder = OkHttpClient.Builder()
|
||||||
@@ -48,13 +43,13 @@ internal class PicassoHandler(private val context: Context, private val settings
|
|||||||
@Throws(IOException::class)
|
@Throws(IOException::class)
|
||||||
fun getImageFromUrl(url: String?): Bitmap = picasso.load(url).get()
|
fun getImageFromUrl(url: String?): Bitmap = picasso.load(url).get()
|
||||||
|
|
||||||
fun getIcon(appId: Long): Bitmap {
|
fun getIcon(app: Application?): Bitmap {
|
||||||
if (appId == -1L) {
|
if (app == null) {
|
||||||
return BitmapFactory.decodeResource(context.resources, R.drawable.gotify)
|
return BitmapFactory.decodeResource(context.resources, R.drawable.gotify)
|
||||||
}
|
}
|
||||||
try {
|
try {
|
||||||
return getImageFromUrl(
|
return getImageFromUrl(
|
||||||
Utils.resolveAbsoluteUrl("${settings.url}/", appIdToAppImage[appId])
|
Utils.resolveAbsoluteUrl("${settings.url}/", app.image)
|
||||||
)
|
)
|
||||||
} catch (e: IOException) {
|
} catch (e: IOException) {
|
||||||
Log.e("Could not load image for notification", e)
|
Log.e("Could not load image for notification", e)
|
||||||
@@ -62,21 +57,6 @@ internal class PicassoHandler(private val context: Context, private val settings
|
|||||||
return BitmapFactory.decodeResource(context.resources, R.drawable.gotify)
|
return BitmapFactory.decodeResource(context.resources, R.drawable.gotify)
|
||||||
}
|
}
|
||||||
|
|
||||||
fun updateAppIds() {
|
|
||||||
ClientFactory.clientToken(settings.url, settings.sslSettings(), settings.token)
|
|
||||||
.createService(ApplicationApi::class.java)
|
|
||||||
.apps
|
|
||||||
.enqueue(
|
|
||||||
Callback.call(
|
|
||||||
onSuccess = Callback.SuccessBody { apps ->
|
|
||||||
appIdToAppImage.clear()
|
|
||||||
appIdToAppImage.putAll(MessageImageCombiner.appIdToImage(apps))
|
|
||||||
},
|
|
||||||
onError = { appIdToAppImage.clear() }
|
|
||||||
)
|
|
||||||
)
|
|
||||||
}
|
|
||||||
|
|
||||||
fun get() = picasso
|
fun get() = picasso
|
||||||
|
|
||||||
@Throws(IOException::class)
|
@Throws(IOException::class)
|
||||||
|
|||||||
@@ -6,7 +6,6 @@ import android.app.NotificationManager
|
|||||||
import android.app.PendingIntent
|
import android.app.PendingIntent
|
||||||
import android.app.Service
|
import android.app.Service
|
||||||
import android.content.Intent
|
import android.content.Intent
|
||||||
import android.content.IntentFilter
|
|
||||||
import android.graphics.Color
|
import android.graphics.Color
|
||||||
import android.net.ConnectivityManager
|
import android.net.ConnectivityManager
|
||||||
import android.net.Uri
|
import android.net.Uri
|
||||||
@@ -23,7 +22,9 @@ import com.github.gotify.Settings
|
|||||||
import com.github.gotify.Utils
|
import com.github.gotify.Utils
|
||||||
import com.github.gotify.api.Callback
|
import com.github.gotify.api.Callback
|
||||||
import com.github.gotify.api.ClientFactory
|
import com.github.gotify.api.ClientFactory
|
||||||
|
import com.github.gotify.client.api.ApplicationApi
|
||||||
import com.github.gotify.client.api.MessageApi
|
import com.github.gotify.client.api.MessageApi
|
||||||
|
import com.github.gotify.client.model.Application
|
||||||
import com.github.gotify.client.model.Message
|
import com.github.gotify.client.model.Message
|
||||||
import com.github.gotify.log.Log
|
import com.github.gotify.log.Log
|
||||||
import com.github.gotify.log.UncaughtExceptionHandler
|
import com.github.gotify.log.UncaughtExceptionHandler
|
||||||
@@ -31,6 +32,7 @@ import com.github.gotify.messages.Extras
|
|||||||
import com.github.gotify.messages.MessagesActivity
|
import com.github.gotify.messages.MessagesActivity
|
||||||
import com.github.gotify.picasso.PicassoHandler
|
import com.github.gotify.picasso.PicassoHandler
|
||||||
import io.noties.markwon.Markwon
|
import io.noties.markwon.Markwon
|
||||||
|
import java.util.concurrent.ConcurrentHashMap
|
||||||
import java.util.concurrent.atomic.AtomicLong
|
import java.util.concurrent.atomic.AtomicLong
|
||||||
|
|
||||||
internal class WebSocketService : Service() {
|
internal class WebSocketService : Service() {
|
||||||
@@ -41,6 +43,7 @@ internal class WebSocketService : Service() {
|
|||||||
|
|
||||||
private lateinit var settings: Settings
|
private lateinit var settings: Settings
|
||||||
private var connection: WebSocketConnection? = null
|
private var connection: WebSocketConnection? = null
|
||||||
|
private val appIdToApp = ConcurrentHashMap<Long, Application>()
|
||||||
|
|
||||||
private val lastReceivedMessage = AtomicLong(NOT_LOADED)
|
private val lastReceivedMessage = AtomicLong(NOT_LOADED)
|
||||||
private lateinit var missingMessageUtil: MissedMessageUtil
|
private lateinit var missingMessageUtil: MissedMessageUtil
|
||||||
@@ -108,10 +111,29 @@ internal class WebSocketService : Service() {
|
|||||||
.onReconnected { notifyMissedNotifications() }
|
.onReconnected { notifyMissedNotifications() }
|
||||||
.start()
|
.start()
|
||||||
|
|
||||||
val intentFilter = IntentFilter()
|
fetchApps()
|
||||||
intentFilter.addAction(ConnectivityManager.CONNECTIVITY_ACTION)
|
}
|
||||||
|
|
||||||
picassoHandler.updateAppIds()
|
private fun fetchApps() {
|
||||||
|
ClientFactory.clientToken(settings.url, settings.sslSettings(), settings.token)
|
||||||
|
.createService(ApplicationApi::class.java)
|
||||||
|
.apps
|
||||||
|
.enqueue(
|
||||||
|
Callback.call(
|
||||||
|
onSuccess = Callback.SuccessBody { apps ->
|
||||||
|
appIdToApp.clear()
|
||||||
|
appIdToApp.putAll(apps.associateBy { it.id })
|
||||||
|
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {
|
||||||
|
NotificationSupport.createChannels(
|
||||||
|
this,
|
||||||
|
(this.getSystemService(NOTIFICATION_SERVICE) as NotificationManager),
|
||||||
|
apps
|
||||||
|
)
|
||||||
|
}
|
||||||
|
},
|
||||||
|
onError = { appIdToApp.clear() }
|
||||||
|
)
|
||||||
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
private fun onClose() {
|
private fun onClose() {
|
||||||
@@ -312,20 +334,31 @@ internal class WebSocketService : Service() {
|
|||||||
PendingIntent.FLAG_UPDATE_CURRENT or PendingIntent.FLAG_IMMUTABLE
|
PendingIntent.FLAG_UPDATE_CURRENT or PendingIntent.FLAG_IMMUTABLE
|
||||||
)
|
)
|
||||||
|
|
||||||
val b = NotificationCompat.Builder(
|
val channelId: String
|
||||||
this,
|
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O &&
|
||||||
NotificationSupport.convertPriorityToChannel(priority)
|
NotificationSupport.areAppChannelsRequested(this)
|
||||||
)
|
) {
|
||||||
|
channelId = NotificationSupport.getChannelID(priority, appId.toString())
|
||||||
|
NotificationSupport.createChannelIfNonexistent(
|
||||||
|
this,
|
||||||
|
appId.toString(),
|
||||||
|
channelId
|
||||||
|
)
|
||||||
|
} else {
|
||||||
|
channelId = NotificationSupport.convertPriorityToChannel(priority)
|
||||||
|
}
|
||||||
|
|
||||||
|
val b = NotificationCompat.Builder(this, channelId)
|
||||||
|
|
||||||
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.N) {
|
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.N) {
|
||||||
showNotificationGroup(priority)
|
showNotificationGroup(channelId)
|
||||||
}
|
}
|
||||||
|
|
||||||
b.setAutoCancel(true)
|
b.setAutoCancel(true)
|
||||||
.setDefaults(Notification.DEFAULT_ALL)
|
.setDefaults(Notification.DEFAULT_ALL)
|
||||||
.setWhen(System.currentTimeMillis())
|
.setWhen(System.currentTimeMillis())
|
||||||
.setSmallIcon(R.drawable.ic_gotify)
|
.setSmallIcon(R.drawable.ic_gotify)
|
||||||
.setLargeIcon(picassoHandler.getIcon(appId))
|
.setLargeIcon(picassoHandler.getIcon(appIdToApp[appId]))
|
||||||
.setTicker("${getString(R.string.app_name)} - $title")
|
.setTicker("${getString(R.string.app_name)} - $title")
|
||||||
.setGroup(NotificationSupport.Group.MESSAGES)
|
.setGroup(NotificationSupport.Group.MESSAGES)
|
||||||
.setContentTitle(title)
|
.setContentTitle(title)
|
||||||
@@ -365,7 +398,7 @@ internal class WebSocketService : Service() {
|
|||||||
}
|
}
|
||||||
|
|
||||||
@RequiresApi(Build.VERSION_CODES.N)
|
@RequiresApi(Build.VERSION_CODES.N)
|
||||||
fun showNotificationGroup(priority: Long) {
|
fun showNotificationGroup(channelId: String) {
|
||||||
val intent = Intent(this, MessagesActivity::class.java)
|
val intent = Intent(this, MessagesActivity::class.java)
|
||||||
val contentIntent = PendingIntent.getActivity(
|
val contentIntent = PendingIntent.getActivity(
|
||||||
this,
|
this,
|
||||||
@@ -376,7 +409,7 @@ internal class WebSocketService : Service() {
|
|||||||
|
|
||||||
val builder = NotificationCompat.Builder(
|
val builder = NotificationCompat.Builder(
|
||||||
this,
|
this,
|
||||||
NotificationSupport.convertPriorityToChannel(priority)
|
channelId
|
||||||
)
|
)
|
||||||
|
|
||||||
builder.setAutoCancel(true)
|
builder.setAutoCancel(true)
|
||||||
|
|||||||
@@ -5,6 +5,7 @@ import android.content.DialogInterface
|
|||||||
import android.content.Intent
|
import android.content.Intent
|
||||||
import android.content.SharedPreferences
|
import android.content.SharedPreferences
|
||||||
import android.content.SharedPreferences.OnSharedPreferenceChangeListener
|
import android.content.SharedPreferences.OnSharedPreferenceChangeListener
|
||||||
|
import android.os.Build
|
||||||
import android.os.Bundle
|
import android.os.Bundle
|
||||||
import android.view.MenuItem
|
import android.view.MenuItem
|
||||||
import android.view.View
|
import android.view.View
|
||||||
@@ -14,6 +15,7 @@ import androidx.preference.ListPreferenceDialogFragmentCompat
|
|||||||
import androidx.preference.Preference
|
import androidx.preference.Preference
|
||||||
import androidx.preference.PreferenceFragmentCompat
|
import androidx.preference.PreferenceFragmentCompat
|
||||||
import androidx.preference.PreferenceManager
|
import androidx.preference.PreferenceManager
|
||||||
|
import androidx.preference.SwitchPreferenceCompat
|
||||||
import com.github.gotify.R
|
import com.github.gotify.R
|
||||||
import com.github.gotify.databinding.SettingsActivityBinding
|
import com.github.gotify.databinding.SettingsActivityBinding
|
||||||
import com.google.android.material.dialog.MaterialAlertDialogBuilder
|
import com.google.android.material.dialog.MaterialAlertDialogBuilder
|
||||||
@@ -48,38 +50,43 @@ internal class SettingsActivity : AppCompatActivity(), OnSharedPreferenceChangeL
|
|||||||
}
|
}
|
||||||
|
|
||||||
override fun onSharedPreferenceChanged(sharedPreferences: SharedPreferences, key: String) {
|
override fun onSharedPreferenceChanged(sharedPreferences: SharedPreferences, key: String) {
|
||||||
if (getString(R.string.setting_key_theme) == key) {
|
when (key) {
|
||||||
ThemeHelper.setTheme(
|
getString(R.string.setting_key_theme) -> {
|
||||||
this,
|
ThemeHelper.setTheme(
|
||||||
sharedPreferences.getString(key, getString(R.string.theme_default))!!
|
this,
|
||||||
)
|
sharedPreferences.getString(key, getString(R.string.theme_default))!!
|
||||||
|
)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
class SettingsFragment : PreferenceFragmentCompat() {
|
class SettingsFragment : PreferenceFragmentCompat() {
|
||||||
override fun onCreatePreferences(savedInstanceState: Bundle?, rootKey: String?) {
|
override fun onCreatePreferences(savedInstanceState: Bundle?, rootKey: String?) {
|
||||||
setPreferencesFromResource(R.xml.root_preferences, rootKey)
|
setPreferencesFromResource(R.xml.root_preferences, rootKey)
|
||||||
|
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {
|
||||||
|
findPreference<SwitchPreferenceCompat>(
|
||||||
|
getString(R.string.setting_key_notification_channels)
|
||||||
|
)?.isEnabled = true
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
|
override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
|
||||||
super.onViewCreated(view, savedInstanceState)
|
super.onViewCreated(view, savedInstanceState)
|
||||||
val messageLayout: ListPreference? =
|
val messageLayout: ListPreference? =
|
||||||
findPreference(getString(R.string.setting_key_message_layout))
|
findPreference(getString(R.string.setting_key_message_layout))
|
||||||
|
val notificationChannels: SwitchPreferenceCompat? =
|
||||||
|
findPreference(getString(R.string.setting_key_notification_channels))
|
||||||
messageLayout?.onPreferenceChangeListener =
|
messageLayout?.onPreferenceChangeListener =
|
||||||
Preference.OnPreferenceChangeListener { _, _ ->
|
Preference.OnPreferenceChangeListener { _, _ ->
|
||||||
MaterialAlertDialogBuilder(requireContext())
|
showRestartDialog()
|
||||||
.setTitle(R.string.setting_message_layout_dialog_title)
|
true
|
||||||
.setMessage(R.string.setting_message_layout_dialog_message)
|
}
|
||||||
.setPositiveButton(
|
notificationChannels?.onPreferenceChangeListener =
|
||||||
getString(R.string.setting_message_layout_dialog_button1)
|
Preference.OnPreferenceChangeListener { _, _ ->
|
||||||
) { _, _ ->
|
if (Build.VERSION.SDK_INT < Build.VERSION_CODES.O) {
|
||||||
restartApp()
|
return@OnPreferenceChangeListener false
|
||||||
}
|
}
|
||||||
.setNegativeButton(
|
showRestartDialog()
|
||||||
getString(R.string.setting_message_layout_dialog_button2),
|
|
||||||
null
|
|
||||||
)
|
|
||||||
.show()
|
|
||||||
true
|
true
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -102,6 +109,17 @@ internal class SettingsActivity : AppCompatActivity(), OnSharedPreferenceChangeL
|
|||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private fun showRestartDialog() {
|
||||||
|
MaterialAlertDialogBuilder(requireContext())
|
||||||
|
.setTitle(R.string.setting_restart_dialog_title)
|
||||||
|
.setMessage(R.string.setting_restart_dialog_message)
|
||||||
|
.setPositiveButton(getString(R.string.setting_restart_dialog_button1)) { _, _ ->
|
||||||
|
restartApp()
|
||||||
|
}
|
||||||
|
.setNegativeButton(getString(R.string.setting_restart_dialog_button2), null)
|
||||||
|
.show()
|
||||||
|
}
|
||||||
|
|
||||||
private fun restartApp() {
|
private fun restartApp() {
|
||||||
val packageManager = requireContext().packageManager
|
val packageManager = requireContext().packageManager
|
||||||
val packageName = requireContext().packageName
|
val packageName = requireContext().packageName
|
||||||
|
|||||||
6
app/src/main/res/layout/preference_switch.xml
Normal file
6
app/src/main/res/layout/preference_switch.xml
Normal file
@@ -0,0 +1,6 @@
|
|||||||
|
<?xml version="1.0" encoding="utf-8"?>
|
||||||
|
<com.google.android.material.materialswitch.MaterialSwitch
|
||||||
|
xmlns:android="http://schemas.android.com/apk/res/android"
|
||||||
|
android:id="@+id/switchWidget"
|
||||||
|
android:layout_width="wrap_content"
|
||||||
|
android:layout_height="wrap_content" />
|
||||||
@@ -34,4 +34,5 @@
|
|||||||
</string-array>
|
</string-array>
|
||||||
<string name="time_format_value_absolute">time_format_absolute</string>
|
<string name="time_format_value_absolute">time_format_absolute</string>
|
||||||
<string name="time_format_value_relative">time_format_relative</string>
|
<string name="time_format_value_relative">time_format_relative</string>
|
||||||
|
<bool name="notification_channels">false</bool>
|
||||||
</resources>
|
</resources>
|
||||||
|
|||||||
@@ -74,12 +74,15 @@
|
|||||||
<string name="setting_key_theme">theme</string>
|
<string name="setting_key_theme">theme</string>
|
||||||
<string name="setting_message_layout">Message layout</string>
|
<string name="setting_message_layout">Message layout</string>
|
||||||
<string name="setting_key_message_layout">message_layout</string>
|
<string name="setting_key_message_layout">message_layout</string>
|
||||||
<string name="setting_message_layout_dialog_title">Restart App?</string>
|
<string name="setting_restart_dialog_title">Restart App?</string>
|
||||||
<string name="setting_message_layout_dialog_message">The change will be effective on next app start.\n\nDo you want to restart now?</string>
|
<string name="setting_restart_dialog_message">The change will be effective on next app start.\n\nDo you want to restart now?</string>
|
||||||
<string name="setting_message_layout_dialog_button1">Restart</string>
|
<string name="setting_restart_dialog_button1">Restart</string>
|
||||||
<string name="setting_message_layout_dialog_button2">Later</string>
|
<string name="setting_restart_dialog_button2">Later</string>
|
||||||
<string name="setting_time_format">Time format</string>
|
<string name="setting_time_format">Time format</string>
|
||||||
<string name="setting_key_time_format">time_format</string>
|
<string name="setting_key_time_format">time_format</string>
|
||||||
|
<string name="setting_notifications">Notifications</string>
|
||||||
|
<string name="setting_notification_channels">Separate app notification channels</string>
|
||||||
|
<string name="setting_key_notification_channels">notification_channels</string>
|
||||||
<string name="push_message">Push message</string>
|
<string name="push_message">Push message</string>
|
||||||
<string name="appListDescription">App:</string>
|
<string name="appListDescription">App:</string>
|
||||||
<string name="priorityDescription">Priority:</string>
|
<string name="priorityDescription">Priority:</string>
|
||||||
@@ -97,4 +100,10 @@
|
|||||||
<item quantity="one">in %d minute</item>
|
<item quantity="one">in %d minute</item>
|
||||||
<item quantity="other">in %d minutes</item>
|
<item quantity="other">in %d minutes</item>
|
||||||
</plurals>
|
</plurals>
|
||||||
|
|
||||||
|
<string name="notification_channel_title_foreground">Gotify foreground notification</string>
|
||||||
|
<string name="notification_channel_title_min">Min priority messages (<1)</string>
|
||||||
|
<string name="notification_channel_title_low">Low priority messages (1–3)</string>
|
||||||
|
<string name="notification_channel_title_normal">Normal priority messages (4–7)</string>
|
||||||
|
<string name="notification_channel_title_high">High priority messages (>7)</string>
|
||||||
</resources>
|
</resources>
|
||||||
|
|||||||
@@ -1,4 +1,4 @@
|
|||||||
<resources>
|
<resources xmlns:tools="http://schemas.android.com/tools">
|
||||||
|
|
||||||
<!-- Base application theme. -->
|
<!-- Base application theme. -->
|
||||||
<style name="AppTheme" parent="Theme.Material3.DayNight">
|
<style name="AppTheme" parent="Theme.Material3.DayNight">
|
||||||
@@ -25,4 +25,8 @@
|
|||||||
|
|
||||||
<style name="AppTheme.PopupOverlay" parent="AppTheme" />
|
<style name="AppTheme.PopupOverlay" parent="AppTheme" />
|
||||||
|
|
||||||
|
<style name="Preference.SwitchPreferenceCompat" parent="@style/Preference.SwitchPreferenceCompat.Material" tools:ignore="ResourceCycle">
|
||||||
|
<item name="widgetLayout">@layout/preference_switch</item>
|
||||||
|
</style>
|
||||||
|
|
||||||
</resources>
|
</resources>
|
||||||
|
|||||||
@@ -24,4 +24,13 @@
|
|||||||
android:title="@string/setting_time_format" />
|
android:title="@string/setting_time_format" />
|
||||||
</PreferenceCategory>
|
</PreferenceCategory>
|
||||||
|
|
||||||
|
<PreferenceCategory app:title="@string/setting_notifications" >
|
||||||
|
<SwitchPreferenceCompat
|
||||||
|
android:enabled="false"
|
||||||
|
android:defaultValue="@bool/notification_channels"
|
||||||
|
android:key="@string/setting_key_notification_channels"
|
||||||
|
android:title="@string/setting_notification_channels"
|
||||||
|
app:singleLineTitle="false" />
|
||||||
|
</PreferenceCategory>
|
||||||
|
|
||||||
</PreferenceScreen>
|
</PreferenceScreen>
|
||||||
|
|||||||
Reference in New Issue
Block a user