Priorities, titles and tags
|
@ -91,4 +91,7 @@ dependencies {
|
||||||
// LiveData
|
// LiveData
|
||||||
implementation "androidx.lifecycle:lifecycle-livedata-ktx:$rootProject.liveDataVersion"
|
implementation "androidx.lifecycle:lifecycle-livedata-ktx:$rootProject.liveDataVersion"
|
||||||
implementation 'androidx.legacy:legacy-support-v4:1.0.0'
|
implementation 'androidx.legacy:legacy-support-v4:1.0.0'
|
||||||
|
|
||||||
|
// Emojis (tags and such)
|
||||||
|
implementation 'com.vdurmont:emoji-java:5.1.1'
|
||||||
}
|
}
|
||||||
|
|
138
app/schemas/io.heckel.ntfy.data.Database/4.json
Normal file
|
@ -0,0 +1,138 @@
|
||||||
|
{
|
||||||
|
"formatVersion": 1,
|
||||||
|
"database": {
|
||||||
|
"version": 4,
|
||||||
|
"identityHash": "06bd845a8d39dd10549f1aeb6b40d7c5",
|
||||||
|
"entities": [
|
||||||
|
{
|
||||||
|
"tableName": "Subscription",
|
||||||
|
"createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`id` INTEGER NOT NULL, `baseUrl` TEXT NOT NULL, `topic` TEXT NOT NULL, `instant` INTEGER NOT NULL, `mutedUntil` INTEGER NOT NULL, PRIMARY KEY(`id`))",
|
||||||
|
"fields": [
|
||||||
|
{
|
||||||
|
"fieldPath": "id",
|
||||||
|
"columnName": "id",
|
||||||
|
"affinity": "INTEGER",
|
||||||
|
"notNull": true
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"fieldPath": "baseUrl",
|
||||||
|
"columnName": "baseUrl",
|
||||||
|
"affinity": "TEXT",
|
||||||
|
"notNull": true
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"fieldPath": "topic",
|
||||||
|
"columnName": "topic",
|
||||||
|
"affinity": "TEXT",
|
||||||
|
"notNull": true
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"fieldPath": "instant",
|
||||||
|
"columnName": "instant",
|
||||||
|
"affinity": "INTEGER",
|
||||||
|
"notNull": true
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"fieldPath": "mutedUntil",
|
||||||
|
"columnName": "mutedUntil",
|
||||||
|
"affinity": "INTEGER",
|
||||||
|
"notNull": true
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"primaryKey": {
|
||||||
|
"columnNames": [
|
||||||
|
"id"
|
||||||
|
],
|
||||||
|
"autoGenerate": false
|
||||||
|
},
|
||||||
|
"indices": [
|
||||||
|
{
|
||||||
|
"name": "index_Subscription_baseUrl_topic",
|
||||||
|
"unique": true,
|
||||||
|
"columnNames": [
|
||||||
|
"baseUrl",
|
||||||
|
"topic"
|
||||||
|
],
|
||||||
|
"createSql": "CREATE UNIQUE INDEX IF NOT EXISTS `index_Subscription_baseUrl_topic` ON `${TABLE_NAME}` (`baseUrl`, `topic`)"
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"foreignKeys": []
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"tableName": "Notification",
|
||||||
|
"createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`id` TEXT NOT NULL, `subscriptionId` INTEGER NOT NULL, `timestamp` INTEGER NOT NULL, `title` TEXT NOT NULL, `message` TEXT NOT NULL, `notificationId` INTEGER NOT NULL, `priority` INTEGER NOT NULL DEFAULT 3, `tags` TEXT NOT NULL, `deleted` INTEGER NOT NULL, PRIMARY KEY(`id`, `subscriptionId`))",
|
||||||
|
"fields": [
|
||||||
|
{
|
||||||
|
"fieldPath": "id",
|
||||||
|
"columnName": "id",
|
||||||
|
"affinity": "TEXT",
|
||||||
|
"notNull": true
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"fieldPath": "subscriptionId",
|
||||||
|
"columnName": "subscriptionId",
|
||||||
|
"affinity": "INTEGER",
|
||||||
|
"notNull": true
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"fieldPath": "timestamp",
|
||||||
|
"columnName": "timestamp",
|
||||||
|
"affinity": "INTEGER",
|
||||||
|
"notNull": true
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"fieldPath": "title",
|
||||||
|
"columnName": "title",
|
||||||
|
"affinity": "TEXT",
|
||||||
|
"notNull": true
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"fieldPath": "message",
|
||||||
|
"columnName": "message",
|
||||||
|
"affinity": "TEXT",
|
||||||
|
"notNull": true
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"fieldPath": "notificationId",
|
||||||
|
"columnName": "notificationId",
|
||||||
|
"affinity": "INTEGER",
|
||||||
|
"notNull": true
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"fieldPath": "priority",
|
||||||
|
"columnName": "priority",
|
||||||
|
"affinity": "INTEGER",
|
||||||
|
"notNull": true,
|
||||||
|
"defaultValue": "3"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"fieldPath": "tags",
|
||||||
|
"columnName": "tags",
|
||||||
|
"affinity": "TEXT",
|
||||||
|
"notNull": true
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"fieldPath": "deleted",
|
||||||
|
"columnName": "deleted",
|
||||||
|
"affinity": "INTEGER",
|
||||||
|
"notNull": true
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"primaryKey": {
|
||||||
|
"columnNames": [
|
||||||
|
"id",
|
||||||
|
"subscriptionId"
|
||||||
|
],
|
||||||
|
"autoGenerate": false
|
||||||
|
},
|
||||||
|
"indices": [],
|
||||||
|
"foreignKeys": []
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"views": [],
|
||||||
|
"setupQueries": [
|
||||||
|
"CREATE TABLE IF NOT EXISTS room_master_table (id INTEGER PRIMARY KEY,identity_hash TEXT)",
|
||||||
|
"INSERT OR REPLACE INTO room_master_table (id,identity_hash) VALUES(42, '06bd845a8d39dd10549f1aeb6b40d7c5')"
|
||||||
|
]
|
||||||
|
}
|
||||||
|
}
|
|
@ -11,6 +11,7 @@
|
||||||
<uses-permission android:name="android.permission.FOREGROUND_SERVICE"/>
|
<uses-permission android:name="android.permission.FOREGROUND_SERVICE"/>
|
||||||
<uses-permission android:name="android.permission.WAKE_LOCK"/>
|
<uses-permission android:name="android.permission.WAKE_LOCK"/>
|
||||||
<uses-permission android:name="android.permission.RECEIVE_BOOT_COMPLETED"/>
|
<uses-permission android:name="android.permission.RECEIVE_BOOT_COMPLETED"/>
|
||||||
|
<uses-permission android:name="android.permission.VIBRATE" />
|
||||||
|
|
||||||
<application
|
<application
|
||||||
android:name=".app.Application"
|
android:name=".app.Application"
|
||||||
|
|
|
@ -5,7 +5,6 @@ import androidx.room.*
|
||||||
import androidx.room.migration.Migration
|
import androidx.room.migration.Migration
|
||||||
import androidx.sqlite.db.SupportSQLiteDatabase
|
import androidx.sqlite.db.SupportSQLiteDatabase
|
||||||
import kotlinx.coroutines.flow.Flow
|
import kotlinx.coroutines.flow.Flow
|
||||||
import java.util.*
|
|
||||||
|
|
||||||
@Entity(indices = [Index(value = ["baseUrl", "topic"], unique = true)])
|
@Entity(indices = [Index(value = ["baseUrl", "topic"], unique = true)])
|
||||||
data class Subscription(
|
data class Subscription(
|
||||||
|
@ -38,17 +37,20 @@ data class SubscriptionWithMetadata(
|
||||||
val lastActive: Long
|
val lastActive: Long
|
||||||
)
|
)
|
||||||
|
|
||||||
@Entity
|
@Entity(primaryKeys = ["id", "subscriptionId"])
|
||||||
data class Notification(
|
data class Notification(
|
||||||
@PrimaryKey val id: String, // TODO make [id, subscriptionId] the primary key
|
@ColumnInfo(name = "id") val id: String,
|
||||||
@ColumnInfo(name = "subscriptionId") val subscriptionId: Long,
|
@ColumnInfo(name = "subscriptionId") val subscriptionId: Long,
|
||||||
@ColumnInfo(name = "timestamp") val timestamp: Long, // Unix timestamp
|
@ColumnInfo(name = "timestamp") val timestamp: Long, // Unix timestamp
|
||||||
|
@ColumnInfo(name = "title") val title: String,
|
||||||
@ColumnInfo(name = "message") val message: String,
|
@ColumnInfo(name = "message") val message: String,
|
||||||
@ColumnInfo(name = "notificationId") val notificationId: Int, // Android notification popup ID
|
@ColumnInfo(name = "notificationId") val notificationId: Int, // Android notification popup ID
|
||||||
|
@ColumnInfo(name = "priority", defaultValue = "3") val priority: Int, // 1=min, 3=default, 5=max
|
||||||
|
@ColumnInfo(name = "tags") val tags: String,
|
||||||
@ColumnInfo(name = "deleted") val deleted: Boolean,
|
@ColumnInfo(name = "deleted") val deleted: Boolean,
|
||||||
)
|
)
|
||||||
|
|
||||||
@androidx.room.Database(entities = [Subscription::class, Notification::class], version = 3)
|
@androidx.room.Database(entities = [Subscription::class, Notification::class], version = 4)
|
||||||
abstract class Database : RoomDatabase() {
|
abstract class Database : RoomDatabase() {
|
||||||
abstract fun subscriptionDao(): SubscriptionDao
|
abstract fun subscriptionDao(): SubscriptionDao
|
||||||
abstract fun notificationDao(): NotificationDao
|
abstract fun notificationDao(): NotificationDao
|
||||||
|
@ -63,6 +65,7 @@ abstract class Database : RoomDatabase() {
|
||||||
.databaseBuilder(context.applicationContext, Database::class.java,"AppDatabase")
|
.databaseBuilder(context.applicationContext, Database::class.java,"AppDatabase")
|
||||||
.addMigrations(MIGRATION_1_2)
|
.addMigrations(MIGRATION_1_2)
|
||||||
.addMigrations(MIGRATION_2_3)
|
.addMigrations(MIGRATION_2_3)
|
||||||
|
.addMigrations(MIGRATION_3_4)
|
||||||
.fallbackToDestructiveMigration()
|
.fallbackToDestructiveMigration()
|
||||||
.build()
|
.build()
|
||||||
this.instance = instance
|
this.instance = instance
|
||||||
|
@ -90,6 +93,15 @@ abstract class Database : RoomDatabase() {
|
||||||
db.execSQL("ALTER TABLE Subscription ADD COLUMN mutedUntil INTEGER NOT NULL DEFAULT('0')")
|
db.execSQL("ALTER TABLE Subscription ADD COLUMN mutedUntil INTEGER NOT NULL DEFAULT('0')")
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private val MIGRATION_3_4 = object : Migration(3, 4) {
|
||||||
|
override fun migrate(db: SupportSQLiteDatabase) {
|
||||||
|
db.execSQL("CREATE TABLE Notification_New (id TEXT NOT NULL, subscriptionId INTEGER NOT NULL, timestamp INTEGER NOT NULL, title TEXT NOT NULL, message TEXT NOT NULL, notificationId INTEGER NOT NULL, priority INTEGER NOT NULL, tags TEXT NOT NULL, deleted INTEGER NOT NULL, PRIMARY KEY(id, subscriptionId))")
|
||||||
|
db.execSQL("INSERT INTO Notification_New SELECT id, subscriptionId, timestamp, '', message, notificationId, 3, '', deleted FROM Notification")
|
||||||
|
db.execSQL("DROP TABLE Notification")
|
||||||
|
db.execSQL("ALTER TABLE Notification_New RENAME TO Notification")
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -1,10 +0,0 @@
|
||||||
package io.heckel.ntfy.data
|
|
||||||
|
|
||||||
fun topicUrl(baseUrl: String, topic: String) = "${baseUrl}/${topic}"
|
|
||||||
fun topicUrlJson(baseUrl: String, topic: String, since: String) = "${topicUrl(baseUrl, topic)}/json?since=$since"
|
|
||||||
fun topicUrlJsonPoll(baseUrl: String, topic: String) = "${topicUrl(baseUrl, topic)}/json?poll=1"
|
|
||||||
fun topicShortUrl(baseUrl: String, topic: String) =
|
|
||||||
topicUrl(baseUrl, topic)
|
|
||||||
.replace("http://", "")
|
|
||||||
.replace("https://", "")
|
|
||||||
|
|
|
@ -4,9 +4,11 @@ import android.util.Log
|
||||||
import androidx.annotation.Keep
|
import androidx.annotation.Keep
|
||||||
import com.google.gson.Gson
|
import com.google.gson.Gson
|
||||||
import io.heckel.ntfy.data.Notification
|
import io.heckel.ntfy.data.Notification
|
||||||
import io.heckel.ntfy.data.topicUrl
|
import io.heckel.ntfy.util.topicUrl
|
||||||
import io.heckel.ntfy.data.topicUrlJson
|
import io.heckel.ntfy.util.topicUrlJson
|
||||||
import io.heckel.ntfy.data.topicUrlJsonPoll
|
import io.heckel.ntfy.util.topicUrlJsonPoll
|
||||||
|
import io.heckel.ntfy.util.toPriority
|
||||||
|
import io.heckel.ntfy.util.joinTags
|
||||||
import okhttp3.*
|
import okhttp3.*
|
||||||
import okhttp3.RequestBody.Companion.toRequestBody
|
import okhttp3.RequestBody.Companion.toRequestBody
|
||||||
import java.io.IOException
|
import java.io.IOException
|
||||||
|
@ -26,12 +28,21 @@ class ApiService {
|
||||||
.readTimeout(77, TimeUnit.SECONDS) // Assuming that keepalive messages are more frequent than this
|
.readTimeout(77, TimeUnit.SECONDS) // Assuming that keepalive messages are more frequent than this
|
||||||
.build()
|
.build()
|
||||||
|
|
||||||
fun publish(baseUrl: String, topic: String, message: String) {
|
fun publish(baseUrl: String, topic: String, message: String, title: String, priority: Int, tags: List<String>) {
|
||||||
val url = topicUrl(baseUrl, topic)
|
val url = topicUrl(baseUrl, topic)
|
||||||
Log.d(TAG, "Publishing to $url")
|
Log.d(TAG, "Publishing to $url")
|
||||||
|
|
||||||
val request = Request.Builder().url(url).put(message.toRequestBody()).build();
|
var builder = Request.Builder()
|
||||||
client.newCall(request).execute().use { response ->
|
.url(url)
|
||||||
|
.addHeader("X-Priority", priority.toString())
|
||||||
|
.put(message.toRequestBody())
|
||||||
|
if (tags.isNotEmpty()) {
|
||||||
|
builder = builder.addHeader("X-Tags", tags.joinToString(","))
|
||||||
|
}
|
||||||
|
if (title.isNotEmpty()) {
|
||||||
|
builder = builder.addHeader("X-Title", title)
|
||||||
|
}
|
||||||
|
client.newCall(builder.build()).execute().use { response ->
|
||||||
if (!response.isSuccessful) {
|
if (!response.isSuccessful) {
|
||||||
throw Exception("Unexpected response ${response.code} when publishing to $url")
|
throw Exception("Unexpected response ${response.code} when publishing to $url")
|
||||||
}
|
}
|
||||||
|
@ -87,7 +98,10 @@ class ApiService {
|
||||||
id = message.id,
|
id = message.id,
|
||||||
subscriptionId = 0, // TO BE SET downstream
|
subscriptionId = 0, // TO BE SET downstream
|
||||||
timestamp = message.time,
|
timestamp = message.time,
|
||||||
|
title = message.title ?: "",
|
||||||
message = message.message,
|
message = message.message,
|
||||||
|
priority = toPriority(message.priority),
|
||||||
|
tags = joinTags(message.tags),
|
||||||
notificationId = Random.nextInt(),
|
notificationId = Random.nextInt(),
|
||||||
deleted = false
|
deleted = false
|
||||||
)
|
)
|
||||||
|
@ -109,7 +123,17 @@ class ApiService {
|
||||||
|
|
||||||
private fun fromString(subscriptionId: Long, s: String): Notification {
|
private fun fromString(subscriptionId: Long, s: String): Notification {
|
||||||
val message = gson.fromJson(s, Message::class.java)
|
val message = gson.fromJson(s, Message::class.java)
|
||||||
return Notification(message.id, subscriptionId, message.time, message.message, notificationId = 0, deleted = false)
|
return Notification(
|
||||||
|
id = message.id,
|
||||||
|
subscriptionId = subscriptionId,
|
||||||
|
timestamp = message.time,
|
||||||
|
title = message.title ?: "",
|
||||||
|
message = message.message,
|
||||||
|
priority = toPriority(message.priority),
|
||||||
|
tags = joinTags(message.tags),
|
||||||
|
notificationId = 0,
|
||||||
|
deleted = false
|
||||||
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
/* This annotation ensures that proguard still works in production builds,
|
/* This annotation ensures that proguard still works in production builds,
|
||||||
|
@ -120,6 +144,9 @@ class ApiService {
|
||||||
val time: Long,
|
val time: Long,
|
||||||
val event: String,
|
val event: String,
|
||||||
val topic: String,
|
val topic: String,
|
||||||
|
val priority: Int?,
|
||||||
|
val tags: List<String>?,
|
||||||
|
val title: String?,
|
||||||
val message: String
|
val message: String
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|
|
@ -6,23 +6,25 @@ import android.app.PendingIntent
|
||||||
import android.app.TaskStackBuilder
|
import android.app.TaskStackBuilder
|
||||||
import android.content.Context
|
import android.content.Context
|
||||||
import android.content.Intent
|
import android.content.Intent
|
||||||
|
import android.graphics.Color
|
||||||
import android.media.RingtoneManager
|
import android.media.RingtoneManager
|
||||||
import android.os.Build
|
import android.os.Build
|
||||||
import android.util.Log
|
import android.util.Log
|
||||||
|
import androidx.annotation.RequiresApi
|
||||||
import androidx.core.app.NotificationCompat
|
import androidx.core.app.NotificationCompat
|
||||||
import androidx.core.content.ContextCompat
|
import androidx.core.content.ContextCompat
|
||||||
import io.heckel.ntfy.R
|
import io.heckel.ntfy.R
|
||||||
import io.heckel.ntfy.data.Notification
|
import io.heckel.ntfy.data.Notification
|
||||||
import io.heckel.ntfy.data.Subscription
|
import io.heckel.ntfy.data.Subscription
|
||||||
import io.heckel.ntfy.data.topicShortUrl
|
import io.heckel.ntfy.util.topicShortUrl
|
||||||
import io.heckel.ntfy.ui.DetailActivity
|
import io.heckel.ntfy.ui.DetailActivity
|
||||||
import io.heckel.ntfy.ui.MainActivity
|
import io.heckel.ntfy.ui.MainActivity
|
||||||
import kotlin.random.Random
|
import io.heckel.ntfy.util.formatMessage
|
||||||
|
import io.heckel.ntfy.util.formatTitle
|
||||||
|
|
||||||
class NotificationService(val context: Context) {
|
class NotificationService(val context: Context) {
|
||||||
fun send(subscription: Subscription, notification: Notification) {
|
fun send(subscription: Subscription, notification: Notification) {
|
||||||
val title = topicShortUrl(subscription.baseUrl, subscription.topic)
|
Log.d(TAG, "Displaying notification $notification")
|
||||||
Log.d(TAG, "Displaying notification $title: ${notification.message}")
|
|
||||||
|
|
||||||
// Create an Intent for the activity you want to start
|
// Create an Intent for the activity you want to start
|
||||||
val intent = Intent(context, DetailActivity::class.java)
|
val intent = Intent(context, DetailActivity::class.java)
|
||||||
|
@ -36,22 +38,33 @@ class NotificationService(val context: Context) {
|
||||||
getPendingIntent(0, PendingIntent.FLAG_UPDATE_CURRENT) // Get the PendingIntent containing the entire back stack
|
getPendingIntent(0, PendingIntent.FLAG_UPDATE_CURRENT) // Get the PendingIntent containing the entire back stack
|
||||||
}
|
}
|
||||||
|
|
||||||
|
val title = formatTitle(subscription, notification)
|
||||||
|
val message = formatMessage(notification)
|
||||||
val defaultSoundUri = RingtoneManager.getDefaultUri(RingtoneManager.TYPE_NOTIFICATION)
|
val defaultSoundUri = RingtoneManager.getDefaultUri(RingtoneManager.TYPE_NOTIFICATION)
|
||||||
val notificationBuilder = NotificationCompat.Builder(context, CHANNEL_ID)
|
val channelId = toChannelId(notification.priority)
|
||||||
|
var notificationBuilder = NotificationCompat.Builder(context, channelId)
|
||||||
.setSmallIcon(R.drawable.ic_notification)
|
.setSmallIcon(R.drawable.ic_notification)
|
||||||
.setColor(ContextCompat.getColor(context, R.color.primaryColor))
|
.setColor(ContextCompat.getColor(context, R.color.primaryColor))
|
||||||
.setContentTitle(title)
|
.setContentTitle(title)
|
||||||
.setContentText(notification.message)
|
.setContentText(message)
|
||||||
.setStyle(NotificationCompat.BigTextStyle().bigText(notification.message))
|
.setStyle(NotificationCompat.BigTextStyle().bigText(message))
|
||||||
.setSound(defaultSoundUri)
|
.setSound(defaultSoundUri)
|
||||||
.setContentIntent(pendingIntent) // Click target for notification
|
.setContentIntent(pendingIntent) // Click target for notification
|
||||||
.setAutoCancel(true) // Cancel when notification is clicked
|
.setAutoCancel(true) // Cancel when notification is clicked
|
||||||
|
|
||||||
|
if (notification.priority == 4) {
|
||||||
|
notificationBuilder = notificationBuilder
|
||||||
|
.setVibrate(longArrayOf(500, 500, 500, 500, 500, 500))
|
||||||
|
.setLights(Color.YELLOW, 3000, 3000)
|
||||||
|
} else if (notification.priority == 5) {
|
||||||
|
notificationBuilder = notificationBuilder
|
||||||
|
.setVibrate(longArrayOf(1000, 500, 1000, 500, 1000, 500))
|
||||||
|
.setLights(Color.RED, 3000, 3000)
|
||||||
|
}
|
||||||
|
|
||||||
val notificationManager = context.getSystemService(Context.NOTIFICATION_SERVICE) as NotificationManager
|
val notificationManager = context.getSystemService(Context.NOTIFICATION_SERVICE) as NotificationManager
|
||||||
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {
|
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {
|
||||||
val channelName = context.getString(R.string.channel_notifications_name) // Show's up in UI
|
createNotificationChannel(notificationManager, notification)
|
||||||
val channel = NotificationChannel(CHANNEL_ID, channelName, NotificationManager.IMPORTANCE_DEFAULT)
|
|
||||||
notificationManager.createNotificationChannel(channel)
|
|
||||||
}
|
}
|
||||||
notificationManager.notify(notification.notificationId, notificationBuilder.build())
|
notificationManager.notify(notification.notificationId, notificationBuilder.build())
|
||||||
}
|
}
|
||||||
|
@ -64,8 +77,34 @@ class NotificationService(val context: Context) {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@RequiresApi(Build.VERSION_CODES.O)
|
||||||
|
private fun createNotificationChannel(notificationManager: NotificationManager, notification: Notification) {
|
||||||
|
val channel = when (notification.priority) {
|
||||||
|
1 -> NotificationChannel(CHANNEL_ID_MIN, context.getString(R.string.channel_notifications_min_name), NotificationManager.IMPORTANCE_MIN)
|
||||||
|
2 -> NotificationChannel(CHANNEL_ID_LOW, context.getString(R.string.channel_notifications_low_name), NotificationManager.IMPORTANCE_LOW)
|
||||||
|
4 -> NotificationChannel(CHANNEL_ID_HIGH, context.getString(R.string.channel_notifications_high_name), NotificationManager.IMPORTANCE_HIGH)
|
||||||
|
5 -> NotificationChannel(CHANNEL_ID_MAX, context.getString(R.string.channel_notifications_max_name), NotificationManager.IMPORTANCE_MAX)
|
||||||
|
else -> NotificationChannel(CHANNEL_ID_DEFAULT, context.getString(R.string.channel_notifications_default_name), NotificationManager.IMPORTANCE_DEFAULT)
|
||||||
|
}
|
||||||
|
notificationManager.createNotificationChannel(channel)
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun toChannelId(priority: Int): String {
|
||||||
|
return when (priority) {
|
||||||
|
1 -> CHANNEL_ID_MIN
|
||||||
|
2 -> CHANNEL_ID_LOW
|
||||||
|
4 -> CHANNEL_ID_HIGH
|
||||||
|
5 -> CHANNEL_ID_MAX
|
||||||
|
else -> CHANNEL_ID_DEFAULT
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
companion object {
|
companion object {
|
||||||
private const val TAG = "NtfyNotificationService"
|
private const val TAG = "NtfyNotificationService"
|
||||||
private const val CHANNEL_ID = "ntfy"
|
private const val CHANNEL_ID_MIN = "ntfy-min"
|
||||||
|
private const val CHANNEL_ID_LOW = "ntfy-low"
|
||||||
|
private const val CHANNEL_ID_DEFAULT = "ntfy"
|
||||||
|
private const val CHANNEL_ID_HIGH = "ntfy-high"
|
||||||
|
private const val CHANNEL_ID_MAX = "ntfy-max"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -4,7 +4,7 @@ import android.util.Log
|
||||||
import io.heckel.ntfy.data.ConnectionState
|
import io.heckel.ntfy.data.ConnectionState
|
||||||
import io.heckel.ntfy.data.Notification
|
import io.heckel.ntfy.data.Notification
|
||||||
import io.heckel.ntfy.data.Subscription
|
import io.heckel.ntfy.data.Subscription
|
||||||
import io.heckel.ntfy.data.topicUrl
|
import io.heckel.ntfy.util.topicUrl
|
||||||
import kotlinx.coroutines.*
|
import kotlinx.coroutines.*
|
||||||
import okhttp3.Call
|
import okhttp3.Call
|
||||||
import java.util.concurrent.atomic.AtomicBoolean
|
import java.util.concurrent.atomic.AtomicBoolean
|
||||||
|
|
|
@ -15,7 +15,7 @@ import io.heckel.ntfy.R
|
||||||
import io.heckel.ntfy.app.Application
|
import io.heckel.ntfy.app.Application
|
||||||
import io.heckel.ntfy.data.ConnectionState
|
import io.heckel.ntfy.data.ConnectionState
|
||||||
import io.heckel.ntfy.data.Subscription
|
import io.heckel.ntfy.data.Subscription
|
||||||
import io.heckel.ntfy.data.topicUrl
|
import io.heckel.ntfy.util.topicUrl
|
||||||
import io.heckel.ntfy.ui.MainActivity
|
import io.heckel.ntfy.ui.MainActivity
|
||||||
import kotlinx.coroutines.*
|
import kotlinx.coroutines.*
|
||||||
import java.util.concurrent.ConcurrentHashMap
|
import java.util.concurrent.ConcurrentHashMap
|
||||||
|
|
|
@ -121,8 +121,10 @@ class AddFragment : DialogFragment() {
|
||||||
if (baseUrls.count() == 1) {
|
if (baseUrls.count() == 1) {
|
||||||
baseUrlLayout.setEndIconDrawable(R.drawable.ic_cancel_gray_24dp)
|
baseUrlLayout.setEndIconDrawable(R.drawable.ic_cancel_gray_24dp)
|
||||||
baseUrlText.setText(baseUrls.first())
|
baseUrlText.setText(baseUrls.first())
|
||||||
} else {
|
} else if (baseUrls.count() > 1) {
|
||||||
baseUrlLayout.setEndIconDrawable(R.drawable.ic_drop_down_gray_24dp)
|
baseUrlLayout.setEndIconDrawable(R.drawable.ic_drop_down_gray_24dp)
|
||||||
|
} else {
|
||||||
|
baseUrlLayout.setEndIconDrawable(0)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -27,11 +27,13 @@ import io.heckel.ntfy.R
|
||||||
import io.heckel.ntfy.app.Application
|
import io.heckel.ntfy.app.Application
|
||||||
import io.heckel.ntfy.data.Notification
|
import io.heckel.ntfy.data.Notification
|
||||||
import io.heckel.ntfy.data.Subscription
|
import io.heckel.ntfy.data.Subscription
|
||||||
import io.heckel.ntfy.data.topicShortUrl
|
import io.heckel.ntfy.util.topicShortUrl
|
||||||
import io.heckel.ntfy.data.topicUrl
|
import io.heckel.ntfy.util.topicUrl
|
||||||
import io.heckel.ntfy.firebase.FirebaseMessenger
|
import io.heckel.ntfy.firebase.FirebaseMessenger
|
||||||
import io.heckel.ntfy.msg.ApiService
|
import io.heckel.ntfy.msg.ApiService
|
||||||
import io.heckel.ntfy.msg.NotificationService
|
import io.heckel.ntfy.msg.NotificationService
|
||||||
|
import io.heckel.ntfy.util.fadeStatusBarColor
|
||||||
|
import io.heckel.ntfy.util.formatDateShort
|
||||||
import kotlinx.coroutines.*
|
import kotlinx.coroutines.*
|
||||||
import java.util.*
|
import java.util.*
|
||||||
import kotlin.random.Random
|
import kotlin.random.Random
|
||||||
|
@ -324,8 +326,12 @@ class DetailActivity : AppCompatActivity(), ActionMode.Callback, NotificationFra
|
||||||
|
|
||||||
lifecycleScope.launch(Dispatchers.IO) {
|
lifecycleScope.launch(Dispatchers.IO) {
|
||||||
try {
|
try {
|
||||||
val message = getString(R.string.detail_test_message, Date().toString())
|
val possibleTags = listOf("warning", "skull", "success", "triangular_flag_on_post", "de", "dog", "rotating_light", "cat", "bike")
|
||||||
api.publish(subscriptionBaseUrl, subscriptionTopic, message)
|
val priority = Random.nextInt(1, 6)
|
||||||
|
val tags = possibleTags.shuffled().take(Random.nextInt(0, 3))
|
||||||
|
val title = if (Random.nextBoolean()) getString(R.string.detail_test_title) else ""
|
||||||
|
val message = getString(R.string.detail_test_message, priority)
|
||||||
|
api.publish(subscriptionBaseUrl, subscriptionTopic, message, title, priority, tags)
|
||||||
} catch (e: Exception) {
|
} catch (e: Exception) {
|
||||||
runOnUiThread {
|
runOnUiThread {
|
||||||
Toast
|
Toast
|
||||||
|
|
|
@ -3,12 +3,16 @@ package io.heckel.ntfy.ui
|
||||||
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.ImageView
|
||||||
import android.widget.TextView
|
import android.widget.TextView
|
||||||
|
import androidx.core.content.ContextCompat
|
||||||
import androidx.recyclerview.widget.DiffUtil
|
import androidx.recyclerview.widget.DiffUtil
|
||||||
import androidx.recyclerview.widget.ListAdapter
|
import androidx.recyclerview.widget.ListAdapter
|
||||||
import androidx.recyclerview.widget.RecyclerView
|
import androidx.recyclerview.widget.RecyclerView
|
||||||
import io.heckel.ntfy.R
|
import io.heckel.ntfy.R
|
||||||
import io.heckel.ntfy.data.Notification
|
import io.heckel.ntfy.data.Notification
|
||||||
|
import io.heckel.ntfy.util.formatMessage
|
||||||
|
import io.heckel.ntfy.util.formatTitle
|
||||||
import java.util.*
|
import java.util.*
|
||||||
|
|
||||||
class DetailAdapter(private val onClick: (Notification) -> Unit, private val onLongClick: (Notification) -> Unit) :
|
class DetailAdapter(private val onClick: (Notification) -> Unit, private val onLongClick: (Notification) -> Unit) :
|
||||||
|
@ -39,20 +43,51 @@ class DetailAdapter(private val onClick: (Notification) -> Unit, private val onL
|
||||||
class DetailViewHolder(itemView: View, private val selected: Set<String>, val onClick: (Notification) -> Unit, val onLongClick: (Notification) -> Unit) :
|
class DetailViewHolder(itemView: View, private val selected: Set<String>, val onClick: (Notification) -> Unit, val onLongClick: (Notification) -> Unit) :
|
||||||
RecyclerView.ViewHolder(itemView) {
|
RecyclerView.ViewHolder(itemView) {
|
||||||
private var notification: Notification? = null
|
private var notification: Notification? = null
|
||||||
|
private val priorityImageView: ImageView = itemView.findViewById(R.id.detail_item_priority_image)
|
||||||
private val dateView: TextView = itemView.findViewById(R.id.detail_item_date_text)
|
private val dateView: TextView = itemView.findViewById(R.id.detail_item_date_text)
|
||||||
|
private val titleView: TextView = itemView.findViewById(R.id.detail_item_title_text)
|
||||||
private val messageView: TextView = itemView.findViewById(R.id.detail_item_message_text)
|
private val messageView: TextView = itemView.findViewById(R.id.detail_item_message_text)
|
||||||
private val newImageView: View = itemView.findViewById(R.id.detail_item_new)
|
private val newImageView: View = itemView.findViewById(R.id.detail_item_new_dot)
|
||||||
|
|
||||||
fun bind(notification: Notification) {
|
fun bind(notification: Notification) {
|
||||||
this.notification = notification
|
this.notification = notification
|
||||||
|
|
||||||
dateView.text = Date(notification.timestamp * 1000).toString()
|
dateView.text = Date(notification.timestamp * 1000).toString()
|
||||||
messageView.text = notification.message
|
messageView.text = formatMessage(notification)
|
||||||
newImageView.visibility = if (notification.notificationId == 0) View.GONE else View.VISIBLE
|
newImageView.visibility = if (notification.notificationId == 0) View.GONE else View.VISIBLE
|
||||||
itemView.setOnClickListener { onClick(notification) }
|
itemView.setOnClickListener { onClick(notification) }
|
||||||
itemView.setOnLongClickListener { onLongClick(notification); true }
|
itemView.setOnLongClickListener { onLongClick(notification); true }
|
||||||
|
if (notification.title != "") {
|
||||||
|
titleView.visibility = View.VISIBLE
|
||||||
|
titleView.text = formatTitle(notification)
|
||||||
|
} else {
|
||||||
|
titleView.visibility = View.GONE
|
||||||
|
}
|
||||||
if (selected.contains(notification.id)) {
|
if (selected.contains(notification.id)) {
|
||||||
itemView.setBackgroundResource(R.color.primarySelectedRowColor);
|
itemView.setBackgroundResource(R.color.primarySelectedRowColor);
|
||||||
}
|
}
|
||||||
|
val ctx = itemView.context
|
||||||
|
when (notification.priority) {
|
||||||
|
1 -> {
|
||||||
|
priorityImageView.visibility = View.VISIBLE
|
||||||
|
priorityImageView.setImageDrawable(ContextCompat.getDrawable(ctx, R.drawable.ic_priority_1_24dp))
|
||||||
|
}
|
||||||
|
2 -> {
|
||||||
|
priorityImageView.visibility = View.VISIBLE
|
||||||
|
priorityImageView.setImageDrawable(ContextCompat.getDrawable(ctx, R.drawable.ic_priority_2_24dp))
|
||||||
|
}
|
||||||
|
3 -> {
|
||||||
|
priorityImageView.visibility = View.GONE
|
||||||
|
}
|
||||||
|
4 -> {
|
||||||
|
priorityImageView.visibility = View.VISIBLE
|
||||||
|
priorityImageView.setImageDrawable(ContextCompat.getDrawable(ctx, R.drawable.ic_priority_4_24dp))
|
||||||
|
}
|
||||||
|
5 -> {
|
||||||
|
priorityImageView.visibility = View.VISIBLE
|
||||||
|
priorityImageView.setImageDrawable(ContextCompat.getDrawable(ctx, R.drawable.ic_priority_5_24dp))
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -22,11 +22,13 @@ import androidx.work.*
|
||||||
import io.heckel.ntfy.R
|
import io.heckel.ntfy.R
|
||||||
import io.heckel.ntfy.app.Application
|
import io.heckel.ntfy.app.Application
|
||||||
import io.heckel.ntfy.data.Subscription
|
import io.heckel.ntfy.data.Subscription
|
||||||
import io.heckel.ntfy.data.topicShortUrl
|
import io.heckel.ntfy.util.topicShortUrl
|
||||||
import io.heckel.ntfy.msg.ApiService
|
import io.heckel.ntfy.msg.ApiService
|
||||||
import io.heckel.ntfy.msg.NotificationService
|
import io.heckel.ntfy.msg.NotificationService
|
||||||
import io.heckel.ntfy.work.PollWorker
|
import io.heckel.ntfy.work.PollWorker
|
||||||
import io.heckel.ntfy.firebase.FirebaseMessenger
|
import io.heckel.ntfy.firebase.FirebaseMessenger
|
||||||
|
import io.heckel.ntfy.util.fadeStatusBarColor
|
||||||
|
import io.heckel.ntfy.util.formatDateShort
|
||||||
import kotlinx.coroutines.Dispatchers
|
import kotlinx.coroutines.Dispatchers
|
||||||
import kotlinx.coroutines.delay
|
import kotlinx.coroutines.delay
|
||||||
import kotlinx.coroutines.isActive
|
import kotlinx.coroutines.isActive
|
||||||
|
|
|
@ -11,11 +11,10 @@ import androidx.recyclerview.widget.RecyclerView
|
||||||
import io.heckel.ntfy.R
|
import io.heckel.ntfy.R
|
||||||
import io.heckel.ntfy.data.ConnectionState
|
import io.heckel.ntfy.data.ConnectionState
|
||||||
import io.heckel.ntfy.data.Subscription
|
import io.heckel.ntfy.data.Subscription
|
||||||
import io.heckel.ntfy.data.topicShortUrl
|
import io.heckel.ntfy.util.topicShortUrl
|
||||||
import java.text.DateFormat
|
import java.text.DateFormat
|
||||||
import java.util.*
|
import java.util.*
|
||||||
|
|
||||||
|
|
||||||
class MainAdapter(private val onClick: (Subscription) -> Unit, private val onLongClick: (Subscription) -> Unit) :
|
class MainAdapter(private val onClick: (Subscription) -> Unit, private val onLongClick: (Subscription) -> Unit) :
|
||||||
ListAdapter<Subscription, MainAdapter.SubscriptionViewHolder>(TopicDiffCallback) {
|
ListAdapter<Subscription, MainAdapter.SubscriptionViewHolder>(TopicDiffCallback) {
|
||||||
val selected = mutableSetOf<Long>() // Subscription IDs
|
val selected = mutableSetOf<Long>() // Subscription IDs
|
||||||
|
|
|
@ -1,22 +0,0 @@
|
||||||
package io.heckel.ntfy.ui
|
|
||||||
|
|
||||||
import android.animation.ArgbEvaluator
|
|
||||||
import android.animation.ValueAnimator
|
|
||||||
import android.view.Window
|
|
||||||
import java.text.DateFormat
|
|
||||||
import java.util.*
|
|
||||||
|
|
||||||
// Status bar color fading to match action bar, see https://stackoverflow.com/q/51150077/1440785
|
|
||||||
fun fadeStatusBarColor(window: Window, fromColor: Int, toColor: Int) {
|
|
||||||
val statusBarColorAnimation = ValueAnimator.ofObject(ArgbEvaluator(), fromColor, toColor)
|
|
||||||
statusBarColorAnimation.addUpdateListener { animator ->
|
|
||||||
val color = animator.animatedValue as Int
|
|
||||||
window.statusBarColor = color
|
|
||||||
}
|
|
||||||
statusBarColorAnimation.start()
|
|
||||||
}
|
|
||||||
|
|
||||||
fun formatDateShort(timestampSecs: Long): String {
|
|
||||||
val mutedUntilDate = Date(timestampSecs*1000)
|
|
||||||
return DateFormat.getDateTimeInstance(DateFormat.SHORT, DateFormat.SHORT).format(mutedUntilDate)
|
|
||||||
}
|
|
95
app/src/main/java/io/heckel/ntfy/util/Util.kt
Normal file
|
@ -0,0 +1,95 @@
|
||||||
|
package io.heckel.ntfy.util
|
||||||
|
|
||||||
|
import android.animation.ArgbEvaluator
|
||||||
|
import android.animation.ValueAnimator
|
||||||
|
import android.view.Window
|
||||||
|
import com.vdurmont.emoji.EmojiManager
|
||||||
|
import io.heckel.ntfy.data.Notification
|
||||||
|
import io.heckel.ntfy.data.Subscription
|
||||||
|
import java.text.DateFormat
|
||||||
|
import java.util.*
|
||||||
|
|
||||||
|
fun topicUrl(baseUrl: String, topic: String) = "${baseUrl}/${topic}"
|
||||||
|
fun topicUrlJson(baseUrl: String, topic: String, since: String) = "${topicUrl(baseUrl, topic)}/json?since=$since"
|
||||||
|
fun topicUrlJsonPoll(baseUrl: String, topic: String) = "${topicUrl(baseUrl, topic)}/json?poll=1"
|
||||||
|
fun topicShortUrl(baseUrl: String, topic: String) =
|
||||||
|
topicUrl(baseUrl, topic)
|
||||||
|
.replace("http://", "")
|
||||||
|
.replace("https://", "")
|
||||||
|
|
||||||
|
fun formatDateShort(timestampSecs: Long): String {
|
||||||
|
val mutedUntilDate = Date(timestampSecs*1000)
|
||||||
|
return DateFormat.getDateTimeInstance(DateFormat.SHORT, DateFormat.SHORT).format(mutedUntilDate)
|
||||||
|
}
|
||||||
|
|
||||||
|
fun toPriority(priority: Int?): Int {
|
||||||
|
if (priority != null && (1..5).contains(priority)) return priority
|
||||||
|
else return 3
|
||||||
|
}
|
||||||
|
|
||||||
|
fun joinTags(tags: List<String>?): String {
|
||||||
|
return tags?.joinToString(",") ?: ""
|
||||||
|
}
|
||||||
|
|
||||||
|
fun toTags(tags: String?): String {
|
||||||
|
return tags ?: ""
|
||||||
|
}
|
||||||
|
|
||||||
|
fun emojify(tags: List<String>): List<String> {
|
||||||
|
return tags.mapNotNull {
|
||||||
|
when (it.toLowerCase()) {
|
||||||
|
"warn", "warning" -> "\u26A0\uFE0F"
|
||||||
|
"success" -> "\u2714\uFE0F"
|
||||||
|
"failure" -> "\u274C"
|
||||||
|
else -> EmojiManager.getForAlias(it)?.unicode
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Prepend tags/emojis to message, but only if there is a non-empty title.
|
||||||
|
* Otherwise the tags will be prepended to the title.
|
||||||
|
*/
|
||||||
|
fun formatMessage(notification: Notification): String {
|
||||||
|
return if (notification.title != "") {
|
||||||
|
notification.message
|
||||||
|
} else {
|
||||||
|
val emojis = emojify(notification.tags.split(","))
|
||||||
|
if (emojis.isEmpty()) {
|
||||||
|
notification.message
|
||||||
|
} else {
|
||||||
|
emojis.joinToString("") + " " + notification.message
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* See above; prepend emojis to title if the title is non-empty.
|
||||||
|
* Otherwise, they are prepended to the message.
|
||||||
|
*/
|
||||||
|
fun formatTitle(subscription: Subscription, notification: Notification): String {
|
||||||
|
return if (notification.title != "") {
|
||||||
|
formatTitle(notification)
|
||||||
|
} else {
|
||||||
|
topicShortUrl(subscription.baseUrl, subscription.topic)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fun formatTitle(notification: Notification): String {
|
||||||
|
val emojis = emojify(notification.tags.split(","))
|
||||||
|
return if (emojis.isEmpty()) {
|
||||||
|
notification.title
|
||||||
|
} else {
|
||||||
|
emojis.joinToString("") + " " + notification.title
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Status bar color fading to match action bar, see https://stackoverflow.com/q/51150077/1440785
|
||||||
|
fun fadeStatusBarColor(window: Window, fromColor: Int, toColor: Int) {
|
||||||
|
val statusBarColorAnimation = ValueAnimator.ofObject(ArgbEvaluator(), fromColor, toColor)
|
||||||
|
statusBarColorAnimation.addUpdateListener { animator ->
|
||||||
|
val color = animator.animatedValue as Int
|
||||||
|
window.statusBarColor = color
|
||||||
|
}
|
||||||
|
statusBarColorAnimation.start()
|
||||||
|
}
|
26
app/src/main/res/drawable/ic_priority_1_24dp.xml
Normal file
|
@ -0,0 +1,26 @@
|
||||||
|
<vector xmlns:android="http://schemas.android.com/apk/res/android"
|
||||||
|
android:width="24dp"
|
||||||
|
android:height="24dp"
|
||||||
|
android:viewportWidth="24"
|
||||||
|
android:viewportHeight="24">
|
||||||
|
<path
|
||||||
|
android:pathData="m12.195,20.8283a1.2747,1.2747 0,0 0,0.6616 -0.1852l6.6466,-4.0372a1.2746,1.2746 0,0 0,0.4275 -1.7511,1.2746 1.2746,0 0,0 -1.7509,-0.4277l-5.9848,3.6353 -5.9848,-3.6353a1.2746,1.2746 0,0 0,-1.7509 0.4277,1.2746 1.2746,0 0,0 0.4275,1.7511l6.6464,4.0372a1.2747,1.2747 0,0 0,0.6618 0.1852z"
|
||||||
|
android:strokeLineJoin="round"
|
||||||
|
android:strokeWidth="0.0919748"
|
||||||
|
android:fillColor="#999999"
|
||||||
|
android:strokeLineCap="round"/>
|
||||||
|
<path
|
||||||
|
android:pathData="m12.195,15.694a1.2747,1.2747 0,0 0,0.6616 -0.1852l6.6466,-4.0372A1.2746,1.2746 0,0 0,19.9307 9.7205,1.2746 1.2746,0 0,0 18.1798,9.2928L12.195,12.9281 6.2102,9.2928a1.2746,1.2746 0,0 0,-1.7509 0.4277,1.2746 1.2746,0 0,0 0.4275,1.7511l6.6464,4.0372a1.2747,1.2747 0,0 0,0.6618 0.1852z"
|
||||||
|
android:strokeLineJoin="round"
|
||||||
|
android:strokeWidth="0.0919748"
|
||||||
|
android:fillColor="#b3b3b3"
|
||||||
|
android:strokeColor="#00000000"
|
||||||
|
android:strokeLineCap="round"/>
|
||||||
|
<path
|
||||||
|
android:pathData="m12.1168,10.4268a1.2747,1.2747 0,0 0,0.6616 -0.1852l6.6466,-4.0372a1.2746,1.2746 0,0 0,0.4275 -1.7511,1.2746 1.2746,0 0,0 -1.7509,-0.4277l-5.9848,3.6353 -5.9848,-3.6353a1.2746,1.2746 0,0 0,-1.7509 0.4277,1.2746 1.2746,0 0,0 0.4275,1.7511L11.455,10.2416a1.2747,1.2747 0,0 0,0.6618 0.1852z"
|
||||||
|
android:strokeLineJoin="round"
|
||||||
|
android:strokeWidth="0.0919748"
|
||||||
|
android:fillColor="#cccccc"
|
||||||
|
android:strokeColor="#00000000"
|
||||||
|
android:strokeLineCap="round"/>
|
||||||
|
</vector>
|
19
app/src/main/res/drawable/ic_priority_2_24dp.xml
Normal file
|
@ -0,0 +1,19 @@
|
||||||
|
<vector xmlns:android="http://schemas.android.com/apk/res/android"
|
||||||
|
android:width="24dp"
|
||||||
|
android:height="24dp"
|
||||||
|
android:viewportWidth="24"
|
||||||
|
android:viewportHeight="24">
|
||||||
|
<path
|
||||||
|
android:pathData="m12.1727,17.7744a1.2747,1.2747 0,0 0,0.6616 -0.1852l6.6466,-4.0372a1.2746,1.2746 0,0 0,0.4275 -1.7511,1.2746 1.2746,0 0,0 -1.7509,-0.4277L12.1727,15.0085 6.1879,11.3731a1.2746,1.2746 0,0 0,-1.7509 0.4277,1.2746 1.2746,0 0,0 0.4275,1.7511l6.6464,4.0372a1.2747,1.2747 0,0 0,0.6618 0.1852z"
|
||||||
|
android:strokeLineJoin="round"
|
||||||
|
android:strokeWidth="0.0919748"
|
||||||
|
android:fillColor="#999999"
|
||||||
|
android:strokeLineCap="round"/>
|
||||||
|
<path
|
||||||
|
android:pathData="m12.1727,12.64a1.2747,1.2747 0,0 0,0.6616 -0.1852L19.4809,8.4177A1.2746,1.2746 0,0 0,19.9084 6.6666,1.2746 1.2746,0 0,0 18.1575,6.2388L12.1727,9.8742 6.1879,6.2388a1.2746,1.2746 0,0 0,-1.7509 0.4277,1.2746 1.2746,0 0,0 0.4275,1.7511l6.6464,4.0372a1.2747,1.2747 0,0 0,0.6618 0.1852z"
|
||||||
|
android:strokeLineJoin="round"
|
||||||
|
android:strokeWidth="0.0919748"
|
||||||
|
android:fillColor="#b3b3b3"
|
||||||
|
android:strokeColor="#00000000"
|
||||||
|
android:strokeLineCap="round"/>
|
||||||
|
</vector>
|
20
app/src/main/res/drawable/ic_priority_4_24dp.xml
Normal file
|
@ -0,0 +1,20 @@
|
||||||
|
<vector xmlns:android="http://schemas.android.com/apk/res/android"
|
||||||
|
android:width="24dp"
|
||||||
|
android:height="24dp"
|
||||||
|
android:viewportWidth="24"
|
||||||
|
android:viewportHeight="24">
|
||||||
|
<path
|
||||||
|
android:pathData="M12.1168,6.5394A1.2747,1.2747 0,0 0,11.4552 6.7246l-6.6466,4.0372a1.2746,1.2746 0,0 0,-0.4275 1.7511,1.2746 1.2746,0 0,0 1.7509,0.4277l5.9848,-3.6353 5.9848,3.6353A1.2746,1.2746 0,0 0,19.8525 12.5129,1.2746 1.2746,0 0,0 19.425,10.7618L12.7786,6.7246A1.2747,1.2747 0,0 0,12.1168 6.5394Z"
|
||||||
|
android:strokeLineJoin="round"
|
||||||
|
android:strokeWidth="0.0919748"
|
||||||
|
android:fillColor="#c60000"
|
||||||
|
android:strokeColor="#00000000"
|
||||||
|
android:strokeLineCap="round"/>
|
||||||
|
<path
|
||||||
|
android:pathData="m12.195,11.8067a1.2747,1.2747 0,0 0,-0.6616 0.1852l-6.6466,4.0372a1.2746,1.2746 0,0 0,-0.4275 1.7511,1.2746 1.2746,0 0,0 1.7509,0.4277l5.9848,-3.6353 5.9848,3.6353a1.2746,1.2746 0,0 0,1.7509 -0.4277,1.2746 1.2746,0 0,0 -0.4275,-1.7511l-6.6464,-4.0372a1.2747,1.2747 0,0 0,-0.6618 -0.1852z"
|
||||||
|
android:strokeLineJoin="round"
|
||||||
|
android:strokeWidth="0.0919748"
|
||||||
|
android:fillColor="#de0000"
|
||||||
|
android:strokeColor="#00000000"
|
||||||
|
android:strokeLineCap="round"/>
|
||||||
|
</vector>
|
26
app/src/main/res/drawable/ic_priority_5_24dp.xml
Normal file
|
@ -0,0 +1,26 @@
|
||||||
|
<vector xmlns:android="http://schemas.android.com/apk/res/android"
|
||||||
|
android:width="24dp"
|
||||||
|
android:height="24dp"
|
||||||
|
android:viewportWidth="24"
|
||||||
|
android:viewportHeight="24">
|
||||||
|
<path
|
||||||
|
android:pathData="M12.1168,3.4051A1.2747,1.2747 0,0 0,11.4552 3.5903L4.8086,7.6275A1.2746,1.2746 0,0 0,4.381 9.3786,1.2746 1.2746,0 0,0 6.132,9.8063L12.1168,6.171 18.1016,9.8063A1.2746,1.2746 0,0 0,19.8525 9.3786,1.2746 1.2746,0 0,0 19.425,7.6275L12.7786,3.5903A1.2747,1.2747 0,0 0,12.1168 3.4051Z"
|
||||||
|
android:strokeLineJoin="round"
|
||||||
|
android:strokeWidth="0.0919748"
|
||||||
|
android:fillColor="#aa0000"
|
||||||
|
android:strokeLineCap="round"/>
|
||||||
|
<path
|
||||||
|
android:pathData="M12.1168,8.5394A1.2747,1.2747 0,0 0,11.4552 8.7246l-6.6466,4.0372a1.2746,1.2746 0,0 0,-0.4275 1.7511,1.2746 1.2746,0 0,0 1.7509,0.4277l5.9848,-3.6353 5.9848,3.6353A1.2746,1.2746 0,0 0,19.8525 14.5129,1.2746 1.2746,0 0,0 19.425,12.7618L12.7786,8.7246A1.2747,1.2747 0,0 0,12.1168 8.5394Z"
|
||||||
|
android:strokeLineJoin="round"
|
||||||
|
android:strokeWidth="0.0919748"
|
||||||
|
android:fillColor="#c60000"
|
||||||
|
android:strokeColor="#00000000"
|
||||||
|
android:strokeLineCap="round"/>
|
||||||
|
<path
|
||||||
|
android:pathData="m12.195,13.8067a1.2747,1.2747 0,0 0,-0.6616 0.1852l-6.6466,4.0372a1.2746,1.2746 0,0 0,-0.4275 1.7511,1.2746 1.2746,0 0,0 1.7509,0.4277l5.9848,-3.6353 5.9848,3.6353a1.2746,1.2746 0,0 0,1.7509 -0.4277,1.2746 1.2746,0 0,0 -0.4275,-1.7511l-6.6464,-4.0372a1.2747,1.2747 0,0 0,-0.6618 -0.1852z"
|
||||||
|
android:strokeLineJoin="round"
|
||||||
|
android:strokeWidth="0.0919748"
|
||||||
|
android:fillColor="#de0000"
|
||||||
|
android:strokeColor="#00000000"
|
||||||
|
android:strokeLineCap="round"/>
|
||||||
|
</vector>
|
|
@ -6,34 +6,58 @@
|
||||||
android:layout_height="wrap_content"
|
android:layout_height="wrap_content"
|
||||||
android:background="?android:attr/selectableItemBackground"
|
android:background="?android:attr/selectableItemBackground"
|
||||||
android:orientation="horizontal" android:clickable="true"
|
android:orientation="horizontal" android:clickable="true"
|
||||||
android:focusable="true" android:paddingBottom="10dp"
|
android:focusable="true"
|
||||||
android:paddingTop="10dp" android:paddingStart="16dp"
|
>
|
||||||
android:paddingEnd="10dp">
|
|
||||||
<TextView
|
<TextView
|
||||||
android:text="Sun, October 31, 2021, 10:43:12"
|
android:text="Sun, October 31, 2021, 10:43:12"
|
||||||
android:layout_width="wrap_content"
|
android:layout_width="wrap_content"
|
||||||
android:layout_height="wrap_content"
|
android:layout_height="wrap_content"
|
||||||
android:id="@+id/detail_item_date_text"
|
android:id="@+id/detail_item_date_text"
|
||||||
android:textAppearance="@style/TextAppearance.AppCompat.Small" app:layout_constraintTop_toTopOf="parent"
|
android:textAppearance="@style/TextAppearance.AppCompat.Small"
|
||||||
app:layout_constraintStart_toStartOf="parent"/>
|
app:layout_constraintTop_toTopOf="parent"
|
||||||
|
android:layout_marginTop="10dp" app:layout_constraintStart_toStartOf="parent"
|
||||||
|
android:layout_marginStart="10dp"/>
|
||||||
<TextView
|
<TextView
|
||||||
android:layout_width="10dp"
|
android:layout_width="10dp"
|
||||||
android:layout_height="10dp" android:id="@+id/detail_item_new"
|
android:layout_height="10dp" android:id="@+id/detail_item_new_dot"
|
||||||
android:layout_gravity="center"
|
android:layout_gravity="center"
|
||||||
android:background="@drawable/ic_circle"
|
android:background="@drawable/ic_circle"
|
||||||
android:gravity="center"
|
android:gravity="center"
|
||||||
app:layout_constraintTop_toTopOf="@+id/detail_item_date_text"
|
app:layout_constraintTop_toTopOf="@+id/detail_item_date_text"
|
||||||
app:layout_constraintBottom_toBottomOf="@+id/detail_item_date_text"
|
app:layout_constraintBottom_toBottomOf="@+id/detail_item_date_text"
|
||||||
android:layout_marginTop="1dp" app:layout_constraintStart_toEndOf="@+id/detail_item_date_text"
|
android:layout_marginTop="1dp"
|
||||||
|
app:layout_constraintStart_toEndOf="@id/detail_item_priority_image"
|
||||||
android:layout_marginStart="5dp"/>
|
android:layout_marginStart="5dp"/>
|
||||||
<TextView
|
<TextView
|
||||||
android:text="This is a very very very long message. It could be as long as 1024 charaters, which is a lot more than you'd think. No, really so far this message is barely 180 characters long. I can't believe how long 1024 bytes are. This is outrageous. Oh you know what, I think I won't type the whole thing. This seems a little too long for a sample text. Well, anyway, it was nice chatting. So far this message is about 400 bytes long. So maybe just double what you see and that's that."
|
android:text="This is a very very very long message. It could be as long as 1024 charaters, which is a lot more than you'd think. No, really so far this message is barely 180 characters long. I can't believe how long 1024 bytes are. This is outrageous. Oh you know what, I think I won't type the whole thing. This seems a little too long for a sample text. Well, anyway, it was nice chatting. So far this message is about 400 bytes long. So maybe just double what you see and that's that."
|
||||||
android:layout_width="match_parent"
|
android:layout_width="0dp"
|
||||||
android:layout_height="wrap_content"
|
android:layout_height="wrap_content"
|
||||||
android:id="@+id/detail_item_message_text"
|
android:id="@+id/detail_item_message_text"
|
||||||
android:textColor="@color/primaryTextColor"
|
android:textColor="@color/primaryTextColor"
|
||||||
android:textAppearance="@style/TextAppearance.AppCompat.Medium"
|
android:textAppearance="@style/TextAppearance.AppCompat.Medium"
|
||||||
app:layout_constraintTop_toBottomOf="@+id/detail_item_date_text"
|
app:layout_constraintTop_toBottomOf="@id/detail_item_title_text"
|
||||||
app:layout_constraintStart_toStartOf="parent" app:layout_constraintEnd_toEndOf="parent"/>
|
app:layout_constraintBottom_toBottomOf="parent"
|
||||||
|
android:layout_marginBottom="10dp"
|
||||||
|
app:layout_constraintStart_toStartOf="parent" android:layout_marginStart="10dp"
|
||||||
|
app:layout_constraintEnd_toEndOf="parent" android:layout_marginEnd="10dp"/>
|
||||||
|
<TextView
|
||||||
|
android:text="This is an optional title. It can also be a little longer but not too long."
|
||||||
|
android:layout_width="0dp"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
android:id="@+id/detail_item_title_text"
|
||||||
|
android:textColor="@color/primaryTextColor"
|
||||||
|
android:textAppearance="@style/TextAppearance.AppCompat.Medium"
|
||||||
|
app:layout_constraintEnd_toEndOf="parent" android:layout_marginEnd="10dp"
|
||||||
|
app:layout_constraintStart_toStartOf="parent"
|
||||||
|
android:layout_marginStart="10dp" android:textStyle="bold"
|
||||||
|
app:layout_constraintTop_toBottomOf="@+id/detail_item_date_text"/>
|
||||||
|
<ImageView
|
||||||
|
android:layout_width="16dp"
|
||||||
|
android:layout_height="16dp" app:srcCompat="@drawable/ic_priority_5_24dp"
|
||||||
|
android:id="@+id/detail_item_priority_image"
|
||||||
|
app:layout_constraintStart_toEndOf="@+id/detail_item_date_text"
|
||||||
|
app:layout_constraintTop_toTopOf="@+id/detail_item_date_text"
|
||||||
|
app:layout_constraintBottom_toBottomOf="@+id/detail_item_date_text" android:layout_marginStart="5dp"/>
|
||||||
</androidx.constraintlayout.widget.ConstraintLayout>
|
</androidx.constraintlayout.widget.ConstraintLayout>
|
||||||
|
|
||||||
|
|
|
@ -9,5 +9,8 @@
|
||||||
|
|
||||||
<color name="primarySelectedRowColor">#EEEEEE</color>
|
<color name="primarySelectedRowColor">#EEEEEE</color>
|
||||||
<color name="primaryDangerButtonColor">#C30000</color>
|
<color name="primaryDangerButtonColor">#C30000</color>
|
||||||
|
|
||||||
|
<color name="primaryPriorityUrgentColor">#C30000</color>
|
||||||
|
<color name="primaryPriorityHighColor">#E10000</color>
|
||||||
</resources>
|
</resources>
|
||||||
|
|
||||||
|
|
|
@ -6,7 +6,11 @@
|
||||||
<string name="app_base_host">ntfy.sh</string> <!-- If changed, you must also change google-services.json! -->
|
<string name="app_base_host">ntfy.sh</string> <!-- If changed, you must also change google-services.json! -->
|
||||||
|
|
||||||
<!-- Notification channels -->
|
<!-- Notification channels -->
|
||||||
<string name="channel_notifications_name">Notifications</string>
|
<string name="channel_notifications_min_name">Notifications (Min Priority)</string>
|
||||||
|
<string name="channel_notifications_low_name">Notifications (Low Priority)</string>
|
||||||
|
<string name="channel_notifications_default_name">Notifications (Default Priority)</string>
|
||||||
|
<string name="channel_notifications_high_name">Notifications (High Priority)</string>
|
||||||
|
<string name="channel_notifications_max_name">Notifications (Max Priority)</string>
|
||||||
<string name="channel_subscriber_service_name">Subscription Service</string>
|
<string name="channel_subscriber_service_name">Subscription Service</string>
|
||||||
<string name="channel_subscriber_notification_title">Listening for incoming notifications</string>
|
<string name="channel_subscriber_notification_title">Listening for incoming notifications</string>
|
||||||
<string name="channel_subscriber_notification_text">You are subscribed to instant delivery topics</string>
|
<string name="channel_subscriber_notification_text">You are subscribed to instant delivery topics</string>
|
||||||
|
@ -88,7 +92,8 @@
|
||||||
</string>
|
</string>
|
||||||
<string name="detail_delete_dialog_permanently_delete">Permanently delete</string>
|
<string name="detail_delete_dialog_permanently_delete">Permanently delete</string>
|
||||||
<string name="detail_delete_dialog_cancel">Cancel</string>
|
<string name="detail_delete_dialog_cancel">Cancel</string>
|
||||||
<string name="detail_test_message">This is a test notification from the Ntfy Android app. It was sent at %1$s.</string>
|
<string name="detail_test_title">Test: You can set a title if you like</string>
|
||||||
|
<string name="detail_test_message">This is a test notification from the Ntfy Android app. It has a priority of %1$d. If you send another one, it may look different.</string>
|
||||||
<string name="detail_test_message_error">Could not send test message: %1$s</string>
|
<string name="detail_test_message_error">Could not send test message: %1$s</string>
|
||||||
<string name="detail_copied_to_clipboard_message">Copied to clipboard</string>
|
<string name="detail_copied_to_clipboard_message">Copied to clipboard</string>
|
||||||
<string name="detail_instant_delivery_enabled">Instant delivery enabled</string>
|
<string name="detail_instant_delivery_enabled">Instant delivery enabled</string>
|
||||||
|
|
|
@ -7,6 +7,9 @@ import io.heckel.ntfy.R
|
||||||
import io.heckel.ntfy.app.Application
|
import io.heckel.ntfy.app.Application
|
||||||
import io.heckel.ntfy.data.Notification
|
import io.heckel.ntfy.data.Notification
|
||||||
import io.heckel.ntfy.msg.NotificationService
|
import io.heckel.ntfy.msg.NotificationService
|
||||||
|
import io.heckel.ntfy.util.joinTags
|
||||||
|
import io.heckel.ntfy.util.toPriority
|
||||||
|
import io.heckel.ntfy.util.toTags
|
||||||
import kotlinx.coroutines.CoroutineScope
|
import kotlinx.coroutines.CoroutineScope
|
||||||
import kotlinx.coroutines.SupervisorJob
|
import kotlinx.coroutines.SupervisorJob
|
||||||
import kotlinx.coroutines.launch
|
import kotlinx.coroutines.launch
|
||||||
|
@ -29,7 +32,10 @@ class FirebaseService : FirebaseMessagingService() {
|
||||||
val id = data["id"]
|
val id = data["id"]
|
||||||
val timestamp = data["time"]?.toLongOrNull()
|
val timestamp = data["time"]?.toLongOrNull()
|
||||||
val topic = data["topic"]
|
val topic = data["topic"]
|
||||||
|
val title = data["title"]
|
||||||
val message = data["message"]
|
val message = data["message"]
|
||||||
|
val priority = data["priority"]?.toIntOrNull()
|
||||||
|
val tags = data["tags"]
|
||||||
if (id == null || topic == null || message == null || timestamp == null) {
|
if (id == null || topic == null || message == null || timestamp == null) {
|
||||||
Log.d(TAG, "Discarding unexpected message: from=${remoteMessage.from}, data=${data}")
|
Log.d(TAG, "Discarding unexpected message: from=${remoteMessage.from}, data=${data}")
|
||||||
return
|
return
|
||||||
|
@ -45,8 +51,11 @@ class FirebaseService : FirebaseMessagingService() {
|
||||||
id = id,
|
id = id,
|
||||||
subscriptionId = subscription.id,
|
subscriptionId = subscription.id,
|
||||||
timestamp = timestamp,
|
timestamp = timestamp,
|
||||||
|
title = title ?: "",
|
||||||
message = message,
|
message = message,
|
||||||
notificationId = Random.nextInt(),
|
notificationId = Random.nextInt(),
|
||||||
|
priority = toPriority(priority),
|
||||||
|
tags = toTags(tags),
|
||||||
deleted = false
|
deleted = false
|
||||||
)
|
)
|
||||||
val shouldNotify = repository.addNotification(notification)
|
val shouldNotify = repository.addNotification(notification)
|
||||||
|
|
1
assets/arrow_drop_down_black_24dp.svg
Normal file
|
@ -0,0 +1 @@
|
||||||
|
<svg xmlns="http://www.w3.org/2000/svg" height="24px" viewBox="0 0 24 24" width="24px" fill="#000000"><path d="M0 0h24v24H0z" fill="none"/><path d="M7 10l5 5 5-5z"/></svg>
|
After Width: | Height: | Size: 171 B |
47
assets/priority_1_24dp.svg
Normal file
|
@ -0,0 +1,47 @@
|
||||||
|
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
|
||||||
|
<svg
|
||||||
|
height="24px"
|
||||||
|
viewBox="0 0 24 24"
|
||||||
|
width="24px"
|
||||||
|
fill="#000000"
|
||||||
|
version="1.1"
|
||||||
|
id="svg1428"
|
||||||
|
sodipodi:docname="priority_1_24dp.svg"
|
||||||
|
inkscape:version="1.1.1 (3bf5ae0, 2021-09-20)"
|
||||||
|
xmlns:inkscape="http://www.inkscape.org/namespaces/inkscape"
|
||||||
|
xmlns:sodipodi="http://sodipodi.sourceforge.net/DTD/sodipodi-0.dtd"
|
||||||
|
xmlns="http://www.w3.org/2000/svg"
|
||||||
|
xmlns:svg="http://www.w3.org/2000/svg">
|
||||||
|
<defs
|
||||||
|
id="defs1432" />
|
||||||
|
<sodipodi:namedview
|
||||||
|
id="namedview1430"
|
||||||
|
pagecolor="#505050"
|
||||||
|
bordercolor="#eeeeee"
|
||||||
|
borderopacity="1"
|
||||||
|
inkscape:pageshadow="0"
|
||||||
|
inkscape:pageopacity="0"
|
||||||
|
inkscape:pagecheckerboard="0"
|
||||||
|
showgrid="false"
|
||||||
|
inkscape:zoom="20.517358"
|
||||||
|
inkscape:cx="22.834324"
|
||||||
|
inkscape:cy="15.742768"
|
||||||
|
inkscape:window-width="1863"
|
||||||
|
inkscape:window-height="1025"
|
||||||
|
inkscape:window-x="57"
|
||||||
|
inkscape:window-y="27"
|
||||||
|
inkscape:window-maximized="1"
|
||||||
|
inkscape:current-layer="svg1428" />
|
||||||
|
<path
|
||||||
|
style="color:#000000;fill:#999999;fill-opacity:1;stroke-width:0.0919748;stroke-linecap:round;stroke-linejoin:round;-inkscape-stroke:none"
|
||||||
|
d="m 12.195014,20.828316 a 1.2747098,1.2747098 0 0 0 0.661605,-0.185206 l 6.646593,-4.037178 a 1.2745823,1.2745823 0 0 0 0.427537,-1.751107 1.2745823,1.2745823 0 0 0 -1.750928,-0.427718 l -5.984807,3.635327 -5.9848086,-3.635327 a 1.2745823,1.2745823 0 0 0 -1.750927,0.427718 1.2745823,1.2745823 0 0 0 0.427536,1.751107 l 6.6464146,4.037178 a 1.2747098,1.2747098 0 0 0 0.661785,0.185206 z"
|
||||||
|
id="rect3554" />
|
||||||
|
<path
|
||||||
|
style="color:#000000;fill:#b3b3b3;fill-opacity:1;stroke:none;stroke-width:0.0919748;stroke-linecap:round;stroke-linejoin:round;-inkscape-stroke:none"
|
||||||
|
d="m 12.195014,15.694014 a 1.2747098,1.2747098 0 0 0 0.661605,-0.185206 l 6.646593,-4.037176 A 1.2745823,1.2745823 0 0 0 19.930749,9.7205243 1.2745823,1.2745823 0 0 0 18.179821,9.2928073 L 12.195014,12.928134 6.2102054,9.2928073 a 1.2745823,1.2745823 0 0 0 -1.750927,0.427717 1.2745823,1.2745823 0 0 0 0.427536,1.7511077 l 6.6464146,4.037176 a 1.2747098,1.2747098 0 0 0 0.661785,0.185206 z"
|
||||||
|
id="path9314" />
|
||||||
|
<path
|
||||||
|
style="color:#000000;fill:#cccccc;fill-opacity:1;stroke:none;stroke-width:0.0919748;stroke-linecap:round;stroke-linejoin:round;-inkscape-stroke:none"
|
||||||
|
d="m 12.116784,10.426777 a 1.2747098,1.2747098 0 0 0 0.661606,-0.185205 l 6.646593,-4.0371767 a 1.2745823,1.2745823 0 0 0 0.427537,-1.751108 1.2745823,1.2745823 0 0 0 -1.750928,-0.427718 l -5.984808,3.635327 -5.9848066,-3.635327 a 1.2745823,1.2745823 0 0 0 -1.750928,0.427718 1.2745823,1.2745823 0 0 0 0.427537,1.751108 L 11.455,10.241572 a 1.2747098,1.2747098 0 0 0 0.661784,0.185205 z"
|
||||||
|
id="path9316" />
|
||||||
|
</svg>
|
After Width: | Height: | Size: 2.7 KiB |
43
assets/priority_2_24dp.svg
Normal file
|
@ -0,0 +1,43 @@
|
||||||
|
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
|
||||||
|
<svg
|
||||||
|
height="24px"
|
||||||
|
viewBox="0 0 24 24"
|
||||||
|
width="24px"
|
||||||
|
fill="#000000"
|
||||||
|
version="1.1"
|
||||||
|
id="svg1428"
|
||||||
|
sodipodi:docname="priority_2_24dp.svg"
|
||||||
|
inkscape:version="1.1.1 (3bf5ae0, 2021-09-20)"
|
||||||
|
xmlns:inkscape="http://www.inkscape.org/namespaces/inkscape"
|
||||||
|
xmlns:sodipodi="http://sodipodi.sourceforge.net/DTD/sodipodi-0.dtd"
|
||||||
|
xmlns="http://www.w3.org/2000/svg"
|
||||||
|
xmlns:svg="http://www.w3.org/2000/svg">
|
||||||
|
<defs
|
||||||
|
id="defs1432" />
|
||||||
|
<sodipodi:namedview
|
||||||
|
id="namedview1430"
|
||||||
|
pagecolor="#505050"
|
||||||
|
bordercolor="#eeeeee"
|
||||||
|
borderopacity="1"
|
||||||
|
inkscape:pageshadow="0"
|
||||||
|
inkscape:pageopacity="0"
|
||||||
|
inkscape:pagecheckerboard="0"
|
||||||
|
showgrid="false"
|
||||||
|
inkscape:zoom="20.517358"
|
||||||
|
inkscape:cx="22.834324"
|
||||||
|
inkscape:cy="15.742768"
|
||||||
|
inkscape:window-width="1863"
|
||||||
|
inkscape:window-height="1025"
|
||||||
|
inkscape:window-x="57"
|
||||||
|
inkscape:window-y="27"
|
||||||
|
inkscape:window-maximized="1"
|
||||||
|
inkscape:current-layer="svg1428" />
|
||||||
|
<path
|
||||||
|
style="color:#000000;fill:#999999;fill-opacity:1;stroke-width:0.0919748;stroke-linecap:round;stroke-linejoin:round;-inkscape-stroke:none"
|
||||||
|
d="m 12.172712,17.774352 a 1.2747098,1.2747098 0 0 0 0.661605,-0.185206 l 6.646593,-4.037178 a 1.2745823,1.2745823 0 0 0 0.427537,-1.751107 1.2745823,1.2745823 0 0 0 -1.750928,-0.427718 L 12.172712,15.00847 6.1879033,11.373143 a 1.2745823,1.2745823 0 0 0 -1.750927,0.427718 1.2745823,1.2745823 0 0 0 0.427536,1.751107 l 6.6464147,4.037178 a 1.2747098,1.2747098 0 0 0 0.661785,0.185206 z"
|
||||||
|
id="rect3554" />
|
||||||
|
<path
|
||||||
|
style="color:#000000;fill:#b3b3b3;fill-opacity:1;stroke:none;stroke-width:0.0919748;stroke-linecap:round;stroke-linejoin:round;-inkscape-stroke:none"
|
||||||
|
d="m 12.172712,12.64005 a 1.2747098,1.2747098 0 0 0 0.661605,-0.185206 L 19.48091,8.4176679 A 1.2745823,1.2745823 0 0 0 19.908447,6.6665602 1.2745823,1.2745823 0 0 0 18.157519,6.2388432 L 12.172712,9.8741699 6.1879033,6.2388432 a 1.2745823,1.2745823 0 0 0 -1.750927,0.427717 1.2745823,1.2745823 0 0 0 0.427536,1.7511077 l 6.6464147,4.0371761 a 1.2747098,1.2747098 0 0 0 0.661785,0.185206 z"
|
||||||
|
id="path9314" />
|
||||||
|
</svg>
|
After Width: | Height: | Size: 2.1 KiB |
43
assets/priority_4_24dp.svg
Normal file
|
@ -0,0 +1,43 @@
|
||||||
|
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
|
||||||
|
<svg
|
||||||
|
height="24px"
|
||||||
|
viewBox="0 0 24 24"
|
||||||
|
width="24px"
|
||||||
|
fill="#000000"
|
||||||
|
version="1.1"
|
||||||
|
id="svg1428"
|
||||||
|
sodipodi:docname="priority_4_24dp.svg"
|
||||||
|
inkscape:version="1.1.1 (3bf5ae0, 2021-09-20)"
|
||||||
|
xmlns:inkscape="http://www.inkscape.org/namespaces/inkscape"
|
||||||
|
xmlns:sodipodi="http://sodipodi.sourceforge.net/DTD/sodipodi-0.dtd"
|
||||||
|
xmlns="http://www.w3.org/2000/svg"
|
||||||
|
xmlns:svg="http://www.w3.org/2000/svg">
|
||||||
|
<defs
|
||||||
|
id="defs1432" />
|
||||||
|
<sodipodi:namedview
|
||||||
|
id="namedview1430"
|
||||||
|
pagecolor="#505050"
|
||||||
|
bordercolor="#eeeeee"
|
||||||
|
borderopacity="1"
|
||||||
|
inkscape:pageshadow="0"
|
||||||
|
inkscape:pageopacity="0"
|
||||||
|
inkscape:pagecheckerboard="0"
|
||||||
|
showgrid="false"
|
||||||
|
inkscape:zoom="20.517358"
|
||||||
|
inkscape:cx="22.834324"
|
||||||
|
inkscape:cy="15.742768"
|
||||||
|
inkscape:window-width="1863"
|
||||||
|
inkscape:window-height="1025"
|
||||||
|
inkscape:window-x="57"
|
||||||
|
inkscape:window-y="27"
|
||||||
|
inkscape:window-maximized="1"
|
||||||
|
inkscape:current-layer="svg1428" />
|
||||||
|
<path
|
||||||
|
style="color:#000000;fill:#c60000;fill-opacity:1;stroke:none;stroke-width:0.0919748;stroke-linecap:round;stroke-linejoin:round;-inkscape-stroke:none"
|
||||||
|
d="M 12.116784,6.5394415 A 1.2747098,1.2747098 0 0 0 11.455179,6.724648 l -6.6465926,4.037176 a 1.2745823,1.2745823 0 0 0 -0.427537,1.751108 1.2745823,1.2745823 0 0 0 1.7509281,0.427717 l 5.9848065,-3.635327 5.984809,3.635327 A 1.2745823,1.2745823 0 0 0 19.85252,12.512932 1.2745823,1.2745823 0 0 0 19.424984,10.761824 L 12.778569,6.724648 A 1.2747098,1.2747098 0 0 0 12.116784,6.5394415 Z"
|
||||||
|
id="path9314" />
|
||||||
|
<path
|
||||||
|
style="color:#000000;fill:#de0000;fill-opacity:1;stroke:none;stroke-width:0.0919748;stroke-linecap:round;stroke-linejoin:round;-inkscape-stroke:none"
|
||||||
|
d="m 12.195014,11.806679 a 1.2747098,1.2747098 0 0 0 -0.661606,0.185205 l -6.6465924,4.037177 a 1.2745823,1.2745823 0 0 0 -0.427537,1.751108 1.2745823,1.2745823 0 0 0 1.750928,0.427718 l 5.9848074,-3.635327 5.984807,3.635327 a 1.2745823,1.2745823 0 0 0 1.750928,-0.427718 1.2745823,1.2745823 0 0 0 -0.427537,-1.751108 l -6.646414,-4.037177 a 1.2747098,1.2747098 0 0 0 -0.661784,-0.185205 z"
|
||||||
|
id="path9316" />
|
||||||
|
</svg>
|
After Width: | Height: | Size: 2.1 KiB |
39
assets/priority_4_alt_24dp.svg
Normal file
|
@ -0,0 +1,39 @@
|
||||||
|
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
|
||||||
|
<svg
|
||||||
|
height="24px"
|
||||||
|
viewBox="0 0 24 24"
|
||||||
|
width="24px"
|
||||||
|
fill="#000000"
|
||||||
|
version="1.1"
|
||||||
|
id="svg1428"
|
||||||
|
sodipodi:docname="priority_4_alt_24dp.svg"
|
||||||
|
inkscape:version="1.1.1 (3bf5ae0, 2021-09-20)"
|
||||||
|
xmlns:inkscape="http://www.inkscape.org/namespaces/inkscape"
|
||||||
|
xmlns:sodipodi="http://sodipodi.sourceforge.net/DTD/sodipodi-0.dtd"
|
||||||
|
xmlns="http://www.w3.org/2000/svg"
|
||||||
|
xmlns:svg="http://www.w3.org/2000/svg">
|
||||||
|
<defs
|
||||||
|
id="defs1432" />
|
||||||
|
<sodipodi:namedview
|
||||||
|
id="namedview1430"
|
||||||
|
pagecolor="#505050"
|
||||||
|
bordercolor="#eeeeee"
|
||||||
|
borderopacity="1"
|
||||||
|
inkscape:pageshadow="0"
|
||||||
|
inkscape:pageopacity="0"
|
||||||
|
inkscape:pagecheckerboard="0"
|
||||||
|
showgrid="false"
|
||||||
|
inkscape:zoom="19.300483"
|
||||||
|
inkscape:cx="8.1604177"
|
||||||
|
inkscape:cy="7.4609534"
|
||||||
|
inkscape:window-width="1863"
|
||||||
|
inkscape:window-height="1025"
|
||||||
|
inkscape:window-x="57"
|
||||||
|
inkscape:window-y="27"
|
||||||
|
inkscape:window-maximized="1"
|
||||||
|
inkscape:current-layer="svg1428" />
|
||||||
|
<path
|
||||||
|
style="color:#000000;fill:#aa0000;fill-opacity:1;stroke-width:0.0878234;stroke-linecap:round;stroke-linejoin:round;-inkscape-stroke:none"
|
||||||
|
d="M 12.073041,8.4104485 A 1.2171741,1.2171741 0 0 0 11.441298,8.5872953 L 5.0947071,12.442249 a 1.2170524,1.2170524 0 0 0 -0.4082396,1.672069 1.2170524,1.2170524 0 0 0 1.6718977,0.408412 l 5.7146758,-3.471242 5.714676,3.471242 a 1.2170524,1.2170524 0 0 0 1.671897,-0.408412 1.2170524,1.2170524 0 0 0 -0.408239,-1.672069 L 12.704955,8.5872953 A 1.2171741,1.2171741 0 0 0 12.073041,8.4104485 Z"
|
||||||
|
id="rect3554" />
|
||||||
|
</svg>
|
After Width: | Height: | Size: 1.6 KiB |
39
assets/priority_5_24dp.svg
Normal file
|
@ -0,0 +1,39 @@
|
||||||
|
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
|
||||||
|
<svg
|
||||||
|
height="24px"
|
||||||
|
viewBox="0 0 24 24"
|
||||||
|
width="24px"
|
||||||
|
fill="#000000"
|
||||||
|
version="1.1"
|
||||||
|
id="svg1428"
|
||||||
|
sodipodi:docname="priority_5_24dp.svg"
|
||||||
|
inkscape:version="1.1.1 (3bf5ae0, 2021-09-20)"
|
||||||
|
xmlns:inkscape="http://www.inkscape.org/namespaces/inkscape"
|
||||||
|
xmlns:sodipodi="http://sodipodi.sourceforge.net/DTD/sodipodi-0.dtd"
|
||||||
|
xmlns="http://www.w3.org/2000/svg"
|
||||||
|
xmlns:svg="http://www.w3.org/2000/svg">
|
||||||
|
<defs
|
||||||
|
id="defs1432" />
|
||||||
|
<sodipodi:namedview
|
||||||
|
id="namedview1430"
|
||||||
|
pagecolor="#505050"
|
||||||
|
bordercolor="#eeeeee"
|
||||||
|
borderopacity="1"
|
||||||
|
inkscape:pageshadow="0"
|
||||||
|
inkscape:pageopacity="0"
|
||||||
|
inkscape:pagecheckerboard="0"
|
||||||
|
showgrid="false"
|
||||||
|
inkscape:zoom="26.806921"
|
||||||
|
inkscape:cx="18.894374"
|
||||||
|
inkscape:cy="14.026229"
|
||||||
|
inkscape:window-width="1863"
|
||||||
|
inkscape:window-height="1025"
|
||||||
|
inkscape:window-x="57"
|
||||||
|
inkscape:window-y="27"
|
||||||
|
inkscape:window-maximized="1"
|
||||||
|
inkscape:current-layer="svg1428" />
|
||||||
|
<path
|
||||||
|
d="M 16.748063,0.46162067 H 7.1927317 L 0.44251841,7.211834 v 9.555331 l 6.75021329,6.750213 h 9.5553313 l 6.750213,-6.750213 V 7.211834 Z M 11.970397,18.778139 c -0.922231,0 -1.665138,-0.742908 -1.665138,-1.665138 0,-0.92223 0.742907,-1.665138 1.665138,-1.665138 0.92223,0 1.665138,0.742908 1.665138,1.665138 0,0.92223 -0.742908,1.665138 -1.665138,1.665138 z m 1.280875,-5.507765 H 10.689521 V 5.5851222 h 2.561751 z"
|
||||||
|
id="path29312"
|
||||||
|
style="fill:#ac0000;fill-opacity:1;stroke-width:1.28088" />
|
||||||
|
</svg>
|
After Width: | Height: | Size: 1.5 KiB |
47
assets/priority_5_alt2_24dp.svg
Normal file
|
@ -0,0 +1,47 @@
|
||||||
|
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
|
||||||
|
<svg
|
||||||
|
height="24px"
|
||||||
|
viewBox="0 0 24 24"
|
||||||
|
width="24px"
|
||||||
|
fill="#000000"
|
||||||
|
version="1.1"
|
||||||
|
id="svg1428"
|
||||||
|
sodipodi:docname="priority_5_24dp.svg"
|
||||||
|
inkscape:version="1.1.1 (3bf5ae0, 2021-09-20)"
|
||||||
|
xmlns:inkscape="http://www.inkscape.org/namespaces/inkscape"
|
||||||
|
xmlns:sodipodi="http://sodipodi.sourceforge.net/DTD/sodipodi-0.dtd"
|
||||||
|
xmlns="http://www.w3.org/2000/svg"
|
||||||
|
xmlns:svg="http://www.w3.org/2000/svg">
|
||||||
|
<defs
|
||||||
|
id="defs1432" />
|
||||||
|
<sodipodi:namedview
|
||||||
|
id="namedview1430"
|
||||||
|
pagecolor="#505050"
|
||||||
|
bordercolor="#eeeeee"
|
||||||
|
borderopacity="1"
|
||||||
|
inkscape:pageshadow="0"
|
||||||
|
inkscape:pageopacity="0"
|
||||||
|
inkscape:pagecheckerboard="0"
|
||||||
|
showgrid="false"
|
||||||
|
inkscape:zoom="20.517358"
|
||||||
|
inkscape:cx="22.834323"
|
||||||
|
inkscape:cy="15.742767"
|
||||||
|
inkscape:window-width="1863"
|
||||||
|
inkscape:window-height="1025"
|
||||||
|
inkscape:window-x="57"
|
||||||
|
inkscape:window-y="27"
|
||||||
|
inkscape:window-maximized="1"
|
||||||
|
inkscape:current-layer="svg1428" />
|
||||||
|
<path
|
||||||
|
style="color:#000000;fill:#aa0000;fill-opacity:1;stroke-width:0.0919748;stroke-linecap:round;stroke-linejoin:round;-inkscape-stroke:none"
|
||||||
|
d="M 12.116784,3.40514 A 1.2747098,1.2747098 0 0 0 11.455179,3.5903463 L 4.8085864,7.6275238 A 1.2745823,1.2745823 0 0 0 4.3810494,9.3786313 1.2745823,1.2745823 0 0 0 6.1319775,9.8063489 L 12.116784,6.1710217 18.101593,9.8063489 A 1.2745823,1.2745823 0 0 0 19.85252,9.3786313 1.2745823,1.2745823 0 0 0 19.424984,7.6275238 L 12.778569,3.5903463 A 1.2747098,1.2747098 0 0 0 12.116784,3.40514 Z"
|
||||||
|
id="rect3554" />
|
||||||
|
<path
|
||||||
|
style="color:#000000;fill:#c60000;fill-opacity:1;stroke:none;stroke-width:0.0919748;stroke-linecap:round;stroke-linejoin:round;-inkscape-stroke:none"
|
||||||
|
d="M 12.116784,8.5394415 A 1.2747098,1.2747098 0 0 0 11.455179,8.724648 l -6.6465926,4.037176 a 1.2745823,1.2745823 0 0 0 -0.427537,1.751108 1.2745823,1.2745823 0 0 0 1.7509281,0.427717 l 5.9848065,-3.635327 5.984809,3.635327 A 1.2745823,1.2745823 0 0 0 19.85252,14.512932 1.2745823,1.2745823 0 0 0 19.424984,12.761824 L 12.778569,8.724648 A 1.2747098,1.2747098 0 0 0 12.116784,8.5394415 Z"
|
||||||
|
id="path9314" />
|
||||||
|
<path
|
||||||
|
style="color:#000000;fill:#de0000;fill-opacity:1;stroke:none;stroke-width:0.0919748;stroke-linecap:round;stroke-linejoin:round;-inkscape-stroke:none"
|
||||||
|
d="m 12.195014,13.806679 a 1.2747098,1.2747098 0 0 0 -0.661606,0.185205 l -6.6465924,4.037177 a 1.2745823,1.2745823 0 0 0 -0.427537,1.751108 1.2745823,1.2745823 0 0 0 1.750928,0.427718 l 5.9848074,-3.635327 5.984807,3.635327 a 1.2745823,1.2745823 0 0 0 1.750928,-0.427718 1.2745823,1.2745823 0 0 0 -0.427537,-1.751108 l -6.646414,-4.037177 a 1.2747098,1.2747098 0 0 0 -0.661784,-0.185205 z"
|
||||||
|
id="path9316" />
|
||||||
|
</svg>
|
After Width: | Height: | Size: 2.7 KiB |
47
assets/priority_5_alt_24dp.svg
Normal file
|
@ -0,0 +1,47 @@
|
||||||
|
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
|
||||||
|
<svg
|
||||||
|
height="24px"
|
||||||
|
viewBox="0 0 24 24"
|
||||||
|
width="24px"
|
||||||
|
fill="#000000"
|
||||||
|
version="1.1"
|
||||||
|
id="svg1428"
|
||||||
|
sodipodi:docname="priority_5_24dp.svg"
|
||||||
|
inkscape:version="1.1.1 (3bf5ae0, 2021-09-20)"
|
||||||
|
xmlns:inkscape="http://www.inkscape.org/namespaces/inkscape"
|
||||||
|
xmlns:sodipodi="http://sodipodi.sourceforge.net/DTD/sodipodi-0.dtd"
|
||||||
|
xmlns="http://www.w3.org/2000/svg"
|
||||||
|
xmlns:svg="http://www.w3.org/2000/svg">
|
||||||
|
<defs
|
||||||
|
id="defs1432" />
|
||||||
|
<sodipodi:namedview
|
||||||
|
id="namedview1430"
|
||||||
|
pagecolor="#505050"
|
||||||
|
bordercolor="#eeeeee"
|
||||||
|
borderopacity="1"
|
||||||
|
inkscape:pageshadow="0"
|
||||||
|
inkscape:pageopacity="0"
|
||||||
|
inkscape:pagecheckerboard="0"
|
||||||
|
showgrid="false"
|
||||||
|
inkscape:zoom="19.300483"
|
||||||
|
inkscape:cx="8.1604177"
|
||||||
|
inkscape:cy="7.4609534"
|
||||||
|
inkscape:window-width="1863"
|
||||||
|
inkscape:window-height="1025"
|
||||||
|
inkscape:window-x="57"
|
||||||
|
inkscape:window-y="27"
|
||||||
|
inkscape:window-maximized="1"
|
||||||
|
inkscape:current-layer="svg1428" />
|
||||||
|
<path
|
||||||
|
style="color:#000000;fill:#ff5630;fill-opacity:1;stroke-width:0.0878234;stroke-linecap:round;stroke-linejoin:round;-inkscape-stroke:none"
|
||||||
|
d="M 12.368788,3.4216973 A 1.2171741,1.2171741 0 0 0 11.737045,3.5985441 L 5.3904546,7.4534983 a 1.2170524,1.2170524 0 0 0 -0.4082396,1.672069 1.2170524,1.2170524 0 0 0 1.6718977,0.408412 l 5.7146753,-3.471242 5.714676,3.471242 a 1.2170524,1.2170524 0 0 0 1.671897,-0.408412 1.2170524,1.2170524 0 0 0 -0.408239,-1.672069 L 13.000702,3.5985441 A 1.2171741,1.2171741 0 0 0 12.368788,3.4216973 Z"
|
||||||
|
id="rect3554" />
|
||||||
|
<path
|
||||||
|
style="color:#000000;fill:#ff7452;fill-opacity:1;stroke:none;stroke-width:0.0878234;stroke-linecap:round;stroke-linejoin:round;-inkscape-stroke:none"
|
||||||
|
d="m 12.368788,8.6910833 a 1.2171741,1.2171741 0 0 0 -0.631743,0.176847 L 5.3904546,12.722883 a 1.2170524,1.2170524 0 0 0 -0.4082396,1.67207 1.2170524,1.2170524 0 0 0 1.6718977,0.408411 l 5.7146753,-3.471242 5.714676,3.471242 a 1.2170524,1.2170524 0 0 0 1.671897,-0.408411 1.2170524,1.2170524 0 0 0 -0.408239,-1.67207 l -6.34642,-3.8549527 a 1.2171741,1.2171741 0 0 0 -0.631914,-0.176847 z"
|
||||||
|
id="path9314" />
|
||||||
|
<path
|
||||||
|
style="color:#000000;fill:#ff8f73;fill-opacity:1;stroke:none;stroke-width:0.0878234;stroke-linecap:round;stroke-linejoin:round;-inkscape-stroke:none"
|
||||||
|
d="m 12.368788,14.136115 a 1.2171741,1.2171741 0 0 0 -0.631743,0.176846 l -6.3465904,3.854954 a 1.2170524,1.2170524 0 0 0 -0.4082396,1.672069 1.2170524,1.2170524 0 0 0 1.6718977,0.408412 l 5.7146753,-3.471242 5.714676,3.471242 a 1.2170524,1.2170524 0 0 0 1.671897,-0.408412 1.2170524,1.2170524 0 0 0 -0.408239,-1.672069 l -6.34642,-3.854954 a 1.2171741,1.2171741 0 0 0 -0.631914,-0.176846 z"
|
||||||
|
id="path9316" />
|
||||||
|
</svg>
|
After Width: | Height: | Size: 2.7 KiB |
1
assets/priority_high_black_24dp.svg
Normal file
|
@ -0,0 +1 @@
|
||||||
|
<svg xmlns="http://www.w3.org/2000/svg" height="24px" viewBox="0 0 24 24" width="24px" fill="#000000"><path d="M0 0h24v24H0V0z" fill="none"/><circle cx="12" cy="19" r="2"/><path d="M10 3h4v12h-4z"/></svg>
|
After Width: | Height: | Size: 204 B |
44
assets/priority_high_circle_red_24dp.svg
Normal file
|
@ -0,0 +1,44 @@
|
||||||
|
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
|
||||||
|
<svg
|
||||||
|
height="24px"
|
||||||
|
viewBox="0 0 24 24"
|
||||||
|
width="24px"
|
||||||
|
fill="#000000"
|
||||||
|
version="1.1"
|
||||||
|
id="svg874"
|
||||||
|
sodipodi:docname="priority_high_circle_red_24dp.svg"
|
||||||
|
inkscape:version="1.1.1 (3bf5ae0, 2021-09-20)"
|
||||||
|
xmlns:inkscape="http://www.inkscape.org/namespaces/inkscape"
|
||||||
|
xmlns:sodipodi="http://sodipodi.sourceforge.net/DTD/sodipodi-0.dtd"
|
||||||
|
xmlns="http://www.w3.org/2000/svg"
|
||||||
|
xmlns:svg="http://www.w3.org/2000/svg">
|
||||||
|
<defs
|
||||||
|
id="defs878" />
|
||||||
|
<sodipodi:namedview
|
||||||
|
id="namedview876"
|
||||||
|
pagecolor="#505050"
|
||||||
|
bordercolor="#eeeeee"
|
||||||
|
borderopacity="1"
|
||||||
|
inkscape:pageshadow="0"
|
||||||
|
inkscape:pageopacity="0"
|
||||||
|
inkscape:pagecheckerboard="0"
|
||||||
|
showgrid="false"
|
||||||
|
inkscape:zoom="24.506368"
|
||||||
|
inkscape:cx="13.894348"
|
||||||
|
inkscape:cy="9.8749843"
|
||||||
|
inkscape:window-width="1863"
|
||||||
|
inkscape:window-height="1025"
|
||||||
|
inkscape:window-x="57"
|
||||||
|
inkscape:window-y="27"
|
||||||
|
inkscape:window-maximized="1"
|
||||||
|
inkscape:current-layer="svg874" />
|
||||||
|
<path
|
||||||
|
id="path25891"
|
||||||
|
style="fill:#800000;stroke-width:34.5068;stroke-linecap:round;stroke-linejoin:round"
|
||||||
|
d="M 11.838253,0.22539283 A 11.693702,11.693702 0 0 0 0.14541272,11.918233 11.693702,11.693702 0 0 0 11.838253,23.612994 11.693702,11.693702 0 0 0 23.533014,11.918233 11.693702,11.693702 0 0 0 11.838253,0.22539283 Z M 11.918883,1.530783 A 10.412385,10.412385 0 0 1 22.331287,11.945109 10.412385,10.412385 0 0 1 11.918881,22.357515 10.412385,10.412385 0 0 1 1.5045542,11.945109 10.412385,10.412385 0 0 1 11.918881,1.530783 Z M 10.095174,3.8612884 V 14.592364 h 3.576385 V 3.8612884 Z m 1.789152,12.5202276 a 1.7886362,1.7886362 0 0 0 -1.789152,1.789152 1.7886362,1.7886362 0 0 0 1.789152,1.787233 1.7886362,1.7886362 0 0 0 1.787233,-1.787233 1.7886362,1.7886362 0 0 0 -1.787233,-1.789152 z" />
|
||||||
|
<path
|
||||||
|
d="M 0.14541272,0.22539283 H 23.734581 V 23.814561 H 0.14541272 Z"
|
||||||
|
fill="none"
|
||||||
|
id="path868"
|
||||||
|
style="stroke-width:0.982882" />
|
||||||
|
</svg>
|
After Width: | Height: | Size: 2 KiB |
1
assets/report_black_24dp.svg
Normal file
|
@ -0,0 +1 @@
|
||||||
|
<svg xmlns="http://www.w3.org/2000/svg" height="24px" viewBox="0 0 24 24" width="24px" fill="#000000"><path d="M0 0h24v24H0z" fill="none"/><path d="M15.73 3H8.27L3 8.27v7.46L8.27 21h7.46L21 15.73V8.27L15.73 3zM12 17.3c-.72 0-1.3-.58-1.3-1.3 0-.72.58-1.3 1.3-1.3.72 0 1.3.58 1.3 1.3 0 .72-.58 1.3-1.3 1.3zm1-4.3h-2V7h2v6z"/></svg>
|
After Width: | Height: | Size: 329 B |
1
assets/warning_black_24dp.svg
Normal file
|
@ -0,0 +1 @@
|
||||||
|
<svg xmlns="http://www.w3.org/2000/svg" height="24px" viewBox="0 0 24 24" width="24px" fill="#000000"><path d="M0 0h24v24H0z" fill="none"/><path d="M1 21h22L12 2 1 21zm12-3h-2v-2h2v2zm0-4h-2v-4h2v4z"/></svg>
|
After Width: | Height: | Size: 207 B |