Compare commits
30 commits
Author | SHA1 | Date | |
---|---|---|---|
|
42b42c623b | ||
|
fc4071fa87 | ||
|
badad8f23a | ||
|
51aed195aa | ||
|
3afd0c2141 | ||
|
e5ca6f5893 | ||
|
389bcf95b2 | ||
|
383ce4bcf5 | ||
|
a155706ffd | ||
|
ee09261f7b | ||
|
05ca1f8977 | ||
|
58b41cc36a | ||
|
3f00769222 | ||
|
a66485805a | ||
|
874e1aed96 | ||
|
0f0c86ce10 | ||
|
734144fe99 | ||
|
d80098cb3d | ||
|
ac98ae8fa8 | ||
|
f5b3689226 | ||
|
3154b943a1 | ||
|
31a1d086b0 | ||
|
997673173a | ||
|
677d55d831 | ||
|
a257f2d83c | ||
|
252b7387ad | ||
|
702c92f91c | ||
|
112eadb926 | ||
|
6cf9146ff4 | ||
|
0d51cc27f4 |
14 changed files with 475 additions and 18 deletions
|
@ -36,6 +36,8 @@ android {
|
||||||
debug {
|
debug {
|
||||||
minifyEnabled false
|
minifyEnabled false
|
||||||
debuggable true
|
debuggable true
|
||||||
|
applicationIdSuffix ".debug"
|
||||||
|
versionNameSuffix "-debug"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -128,4 +130,15 @@ dependencies {
|
||||||
|
|
||||||
// Image viewer
|
// Image viewer
|
||||||
implementation 'com.github.stfalcon-studio:StfalconImageViewer:v1.0.1'
|
implementation 'com.github.stfalcon-studio:StfalconImageViewer:v1.0.1'
|
||||||
|
|
||||||
|
// Better click handling for links
|
||||||
|
implementation 'me.saket:better-link-movement-method:2.2.0'
|
||||||
|
|
||||||
|
// Markdown
|
||||||
|
implementation 'io.noties.markwon:core:4.6.2'
|
||||||
|
implementation 'io.noties.markwon:image-picasso:4.6.2'
|
||||||
|
implementation 'io.noties.markwon:image:4.6.2'
|
||||||
|
implementation 'io.noties.markwon:linkify:4.6.2'
|
||||||
|
implementation 'io.noties.markwon:ext-tables:4.6.2'
|
||||||
|
implementation 'io.noties.markwon:ext-strikethrough:4.6.2'
|
||||||
}
|
}
|
||||||
|
|
|
@ -2,7 +2,7 @@
|
||||||
"formatVersion": 1,
|
"formatVersion": 1,
|
||||||
"database": {
|
"database": {
|
||||||
"version": 13,
|
"version": 13,
|
||||||
"identityHash": "44fc291d937fdf02b9bc2d0abb10d2e0",
|
"identityHash": "208f16743f21d9c374f1314878eb93cb",
|
||||||
"entities": [
|
"entities": [
|
||||||
{
|
{
|
||||||
"tableName": "Subscription",
|
"tableName": "Subscription",
|
||||||
|
@ -94,10 +94,10 @@
|
||||||
}
|
}
|
||||||
],
|
],
|
||||||
"primaryKey": {
|
"primaryKey": {
|
||||||
|
"autoGenerate": false,
|
||||||
"columnNames": [
|
"columnNames": [
|
||||||
"id"
|
"id"
|
||||||
],
|
]
|
||||||
"autoGenerate": false
|
|
||||||
},
|
},
|
||||||
"indices": [
|
"indices": [
|
||||||
{
|
{
|
||||||
|
@ -124,7 +124,7 @@
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"tableName": "Notification",
|
"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, `encoding` TEXT NOT NULL, `notificationId` INTEGER NOT NULL, `priority` INTEGER NOT NULL DEFAULT 3, `tags` TEXT NOT NULL, `click` TEXT NOT NULL, `actions` TEXT, `deleted` INTEGER NOT NULL, `icon_url` TEXT, `icon_contentUri` TEXT, `attachment_name` TEXT, `attachment_type` TEXT, `attachment_size` INTEGER, `attachment_expires` INTEGER, `attachment_url` TEXT, `attachment_contentUri` TEXT, `attachment_progress` INTEGER, PRIMARY KEY(`id`, `subscriptionId`))",
|
"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, `contentType` TEXT NOT NULL, `encoding` TEXT NOT NULL, `notificationId` INTEGER NOT NULL, `priority` INTEGER NOT NULL DEFAULT 3, `tags` TEXT NOT NULL, `click` TEXT NOT NULL, `actions` TEXT, `deleted` INTEGER NOT NULL, `icon_url` TEXT, `icon_contentUri` TEXT, `attachment_name` TEXT, `attachment_type` TEXT, `attachment_size` INTEGER, `attachment_expires` INTEGER, `attachment_url` TEXT, `attachment_contentUri` TEXT, `attachment_progress` INTEGER, PRIMARY KEY(`id`, `subscriptionId`))",
|
||||||
"fields": [
|
"fields": [
|
||||||
{
|
{
|
||||||
"fieldPath": "id",
|
"fieldPath": "id",
|
||||||
|
@ -156,6 +156,12 @@
|
||||||
"affinity": "TEXT",
|
"affinity": "TEXT",
|
||||||
"notNull": true
|
"notNull": true
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
"fieldPath": "contentType",
|
||||||
|
"columnName": "contentType",
|
||||||
|
"affinity": "TEXT",
|
||||||
|
"notNull": true
|
||||||
|
},
|
||||||
{
|
{
|
||||||
"fieldPath": "encoding",
|
"fieldPath": "encoding",
|
||||||
"columnName": "encoding",
|
"columnName": "encoding",
|
||||||
|
@ -255,11 +261,11 @@
|
||||||
}
|
}
|
||||||
],
|
],
|
||||||
"primaryKey": {
|
"primaryKey": {
|
||||||
|
"autoGenerate": false,
|
||||||
"columnNames": [
|
"columnNames": [
|
||||||
"id",
|
"id",
|
||||||
"subscriptionId"
|
"subscriptionId"
|
||||||
],
|
]
|
||||||
"autoGenerate": false
|
|
||||||
},
|
},
|
||||||
"indices": [],
|
"indices": [],
|
||||||
"foreignKeys": []
|
"foreignKeys": []
|
||||||
|
@ -288,10 +294,10 @@
|
||||||
}
|
}
|
||||||
],
|
],
|
||||||
"primaryKey": {
|
"primaryKey": {
|
||||||
|
"autoGenerate": false,
|
||||||
"columnNames": [
|
"columnNames": [
|
||||||
"baseUrl"
|
"baseUrl"
|
||||||
],
|
]
|
||||||
"autoGenerate": false
|
|
||||||
},
|
},
|
||||||
"indices": [],
|
"indices": [],
|
||||||
"foreignKeys": []
|
"foreignKeys": []
|
||||||
|
@ -338,10 +344,10 @@
|
||||||
}
|
}
|
||||||
],
|
],
|
||||||
"primaryKey": {
|
"primaryKey": {
|
||||||
|
"autoGenerate": true,
|
||||||
"columnNames": [
|
"columnNames": [
|
||||||
"id"
|
"id"
|
||||||
],
|
]
|
||||||
"autoGenerate": true
|
|
||||||
},
|
},
|
||||||
"indices": [],
|
"indices": [],
|
||||||
"foreignKeys": []
|
"foreignKeys": []
|
||||||
|
@ -350,7 +356,7 @@
|
||||||
"views": [],
|
"views": [],
|
||||||
"setupQueries": [
|
"setupQueries": [
|
||||||
"CREATE TABLE IF NOT EXISTS room_master_table (id INTEGER PRIMARY KEY,identity_hash TEXT)",
|
"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, '44fc291d937fdf02b9bc2d0abb10d2e0')"
|
"INSERT OR REPLACE INTO room_master_table (id,identity_hash) VALUES(42, '208f16743f21d9c374f1314878eb93cb')"
|
||||||
]
|
]
|
||||||
}
|
}
|
||||||
}
|
}
|
4
app/src/debug/res/values/values.xml
Normal file
4
app/src/debug/res/values/values.xml
Normal file
|
@ -0,0 +1,4 @@
|
||||||
|
<?xml version="1.0" encoding="utf-8"?>
|
||||||
|
<resources>
|
||||||
|
<string name="app_name" translatable="false">ntfy (debug)</string>
|
||||||
|
</resources>
|
|
@ -183,6 +183,7 @@ class Backuper(val context: Context) {
|
||||||
timestamp = n.timestamp,
|
timestamp = n.timestamp,
|
||||||
title = n.title,
|
title = n.title,
|
||||||
message = n.message,
|
message = n.message,
|
||||||
|
contentType = n.contentType,
|
||||||
encoding = n.encoding,
|
encoding = n.encoding,
|
||||||
notificationId = 0,
|
notificationId = 0,
|
||||||
priority = n.priority,
|
priority = n.priority,
|
||||||
|
@ -312,6 +313,7 @@ class Backuper(val context: Context) {
|
||||||
timestamp = n.timestamp,
|
timestamp = n.timestamp,
|
||||||
title = n.title,
|
title = n.title,
|
||||||
message = n.message,
|
message = n.message,
|
||||||
|
contentType = n.contentType,
|
||||||
encoding = n.encoding,
|
encoding = n.encoding,
|
||||||
priority = n.priority,
|
priority = n.priority,
|
||||||
tags = n.tags,
|
tags = n.tags,
|
||||||
|
@ -386,6 +388,7 @@ data class Notification(
|
||||||
val timestamp: Long,
|
val timestamp: Long,
|
||||||
val title: String,
|
val title: String,
|
||||||
val message: String,
|
val message: String,
|
||||||
|
val contentType: String, // "" or "text/markdown" (empty assumes "text/plain")
|
||||||
val encoding: String, // "base64" or ""
|
val encoding: String, // "base64" or ""
|
||||||
val priority: Int, // 1=min, 3=default, 5=max
|
val priority: Int, // 1=min, 3=default, 5=max
|
||||||
val tags: String,
|
val tags: String,
|
||||||
|
|
|
@ -99,6 +99,7 @@ data class Notification(
|
||||||
@ColumnInfo(name = "timestamp") val timestamp: Long, // Unix timestamp
|
@ColumnInfo(name = "timestamp") val timestamp: Long, // Unix timestamp
|
||||||
@ColumnInfo(name = "title") val title: String,
|
@ColumnInfo(name = "title") val title: String,
|
||||||
@ColumnInfo(name = "message") val message: String,
|
@ColumnInfo(name = "message") val message: String,
|
||||||
|
@ColumnInfo(name = "contentType") val contentType: String, // "" or "text/markdown" (empty assume text/plain)
|
||||||
@ColumnInfo(name = "encoding") val encoding: String, // "base64" or ""
|
@ColumnInfo(name = "encoding") val encoding: String, // "base64" or ""
|
||||||
@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 = "priority", defaultValue = "3") val priority: Int, // 1=min, 3=default, 5=max
|
||||||
|
@ -110,6 +111,10 @@ data class Notification(
|
||||||
@ColumnInfo(name = "deleted") val deleted: Boolean,
|
@ColumnInfo(name = "deleted") val deleted: Boolean,
|
||||||
)
|
)
|
||||||
|
|
||||||
|
fun Notification.isMarkdown(): Boolean {
|
||||||
|
return contentType == "text/markdown"
|
||||||
|
}
|
||||||
|
|
||||||
@Entity
|
@Entity
|
||||||
data class Attachment(
|
data class Attachment(
|
||||||
@ColumnInfo(name = "name") val name: String, // Filename
|
@ColumnInfo(name = "name") val name: String, // Filename
|
||||||
|
|
|
@ -28,6 +28,7 @@ class BroadcastService(private val ctx: Context) {
|
||||||
intent.putExtra("message", decodeMessage(notification))
|
intent.putExtra("message", decodeMessage(notification))
|
||||||
intent.putExtra("message_bytes", decodeBytesMessage(notification))
|
intent.putExtra("message_bytes", decodeBytesMessage(notification))
|
||||||
intent.putExtra("message_encoding", notification.encoding)
|
intent.putExtra("message_encoding", notification.encoding)
|
||||||
|
intent.putExtra("content_type", notification.contentType)
|
||||||
intent.putExtra("tags", notification.tags)
|
intent.putExtra("tags", notification.tags)
|
||||||
intent.putExtra("tags_map", joinTagsMap(splitTags(notification.tags)))
|
intent.putExtra("tags_map", joinTagsMap(splitTags(notification.tags)))
|
||||||
intent.putExtra("priority", notification.priority)
|
intent.putExtra("priority", notification.priority)
|
||||||
|
|
|
@ -1,6 +1,7 @@
|
||||||
package io.heckel.ntfy.msg
|
package io.heckel.ntfy.msg
|
||||||
|
|
||||||
import androidx.annotation.Keep
|
import androidx.annotation.Keep
|
||||||
|
import com.google.gson.annotations.SerializedName
|
||||||
|
|
||||||
/* This annotation ensures that proguard still works in production builds,
|
/* This annotation ensures that proguard still works in production builds,
|
||||||
* see https://stackoverflow.com/a/62753300/1440785 */
|
* see https://stackoverflow.com/a/62753300/1440785 */
|
||||||
|
@ -17,6 +18,7 @@ data class Message(
|
||||||
val actions: List<MessageAction>?,
|
val actions: List<MessageAction>?,
|
||||||
val title: String?,
|
val title: String?,
|
||||||
val message: String,
|
val message: String,
|
||||||
|
@SerializedName("content_type") val contentType: String?,
|
||||||
val encoding: String?,
|
val encoding: String?,
|
||||||
val attachment: MessageAttachment?,
|
val attachment: MessageAttachment?,
|
||||||
)
|
)
|
||||||
|
|
|
@ -57,6 +57,7 @@ class NotificationParser {
|
||||||
timestamp = message.time,
|
timestamp = message.time,
|
||||||
title = message.title ?: "",
|
title = message.title ?: "",
|
||||||
message = message.message,
|
message = message.message,
|
||||||
|
contentType = message.contentType ?: "",
|
||||||
encoding = message.encoding ?: "",
|
encoding = message.encoding ?: "",
|
||||||
priority = toPriority(message.priority),
|
priority = toPriority(message.priority),
|
||||||
tags = joinTags(message.tags),
|
tags = joinTags(message.tags),
|
||||||
|
|
|
@ -11,6 +11,8 @@ import android.media.RingtoneManager
|
||||||
import android.net.Uri
|
import android.net.Uri
|
||||||
import android.os.Build
|
import android.os.Build
|
||||||
import android.os.Bundle
|
import android.os.Bundle
|
||||||
|
import android.text.SpannedString
|
||||||
|
import android.text.style.CharacterStyle
|
||||||
import android.widget.Toast
|
import android.widget.Toast
|
||||||
import androidx.core.app.NotificationCompat
|
import androidx.core.app.NotificationCompat
|
||||||
import androidx.core.content.ContextCompat
|
import androidx.core.content.ContextCompat
|
||||||
|
@ -26,6 +28,7 @@ import java.util.*
|
||||||
class NotificationService(val context: Context) {
|
class NotificationService(val context: Context) {
|
||||||
private val notificationManager = context.getSystemService(Context.NOTIFICATION_SERVICE) as NotificationManager
|
private val notificationManager = context.getSystemService(Context.NOTIFICATION_SERVICE) as NotificationManager
|
||||||
private val repository = Repository.getInstance(context)
|
private val repository = Repository.getInstance(context)
|
||||||
|
private val markwon = MarkwonFactory.createForNotification(context)
|
||||||
|
|
||||||
fun display(subscription: Subscription, notification: Notification) {
|
fun display(subscription: Subscription, notification: Notification) {
|
||||||
Log.d(TAG, "Displaying notification $notification")
|
Log.d(TAG, "Displaying notification $notification")
|
||||||
|
@ -147,7 +150,7 @@ class NotificationService(val context: Context) {
|
||||||
try {
|
try {
|
||||||
val attachmentBitmap = contentUri.readBitmapFromUri(context)
|
val attachmentBitmap = contentUri.readBitmapFromUri(context)
|
||||||
builder
|
builder
|
||||||
.setContentText(maybeAppendActionErrors(formatMessage(notification), notification))
|
.setContentText(maybeAppendActionErrors(maybeMarkdown(formatMessage(notification), notification), notification))
|
||||||
.setLargeIcon(attachmentBitmap)
|
.setLargeIcon(attachmentBitmap)
|
||||||
.setStyle(NotificationCompat.BigPictureStyle()
|
.setStyle(NotificationCompat.BigPictureStyle()
|
||||||
.bigPicture(attachmentBitmap)
|
.bigPicture(attachmentBitmap)
|
||||||
|
@ -167,8 +170,8 @@ class NotificationService(val context: Context) {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private fun formatMessageMaybeWithAttachmentInfos(notification: Notification): String {
|
private fun formatMessageMaybeWithAttachmentInfos(notification: Notification): CharSequence {
|
||||||
val message = formatMessage(notification)
|
val message = maybeMarkdown(formatMessage(notification), notification)
|
||||||
val attachment = notification.attachment ?: return message
|
val attachment = notification.attachment ?: return message
|
||||||
val attachmentInfos = if (attachment.size != null) {
|
val attachmentInfos = if (attachment.size != null) {
|
||||||
"${attachment.name}, ${formatBytes(attachment.size)}"
|
"${attachment.name}, ${formatBytes(attachment.size)}"
|
||||||
|
@ -514,6 +517,13 @@ class NotificationService(val context: Context) {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private fun maybeMarkdown(message: String, notification: Notification): CharSequence {
|
||||||
|
if (notification.contentType == "text/markdown") {
|
||||||
|
return markwon.toMarkdown(message)
|
||||||
|
}
|
||||||
|
return message
|
||||||
|
}
|
||||||
|
|
||||||
companion object {
|
companion object {
|
||||||
const val ACTION_VIEW = "view"
|
const val ACTION_VIEW = "view"
|
||||||
const val ACTION_HTTP = "http"
|
const val ACTION_HTTP = "http"
|
||||||
|
|
|
@ -9,6 +9,8 @@ import android.net.Uri
|
||||||
import android.os.Build
|
import android.os.Build
|
||||||
import android.os.Environment
|
import android.os.Environment
|
||||||
import android.provider.MediaStore
|
import android.provider.MediaStore
|
||||||
|
import android.text.method.LinkMovementMethod
|
||||||
|
import android.text.util.Linkify
|
||||||
import android.view.LayoutInflater
|
import android.view.LayoutInflater
|
||||||
import android.view.View
|
import android.view.View
|
||||||
import android.view.ViewGroup
|
import android.view.ViewGroup
|
||||||
|
@ -33,20 +35,23 @@ import io.heckel.ntfy.msg.DownloadType
|
||||||
import io.heckel.ntfy.msg.NotificationService
|
import io.heckel.ntfy.msg.NotificationService
|
||||||
import io.heckel.ntfy.msg.NotificationService.Companion.ACTION_VIEW
|
import io.heckel.ntfy.msg.NotificationService.Companion.ACTION_VIEW
|
||||||
import io.heckel.ntfy.util.*
|
import io.heckel.ntfy.util.*
|
||||||
|
import io.noties.markwon.Markwon
|
||||||
import kotlinx.coroutines.CoroutineScope
|
import kotlinx.coroutines.CoroutineScope
|
||||||
import kotlinx.coroutines.Dispatchers
|
import kotlinx.coroutines.Dispatchers
|
||||||
import kotlinx.coroutines.GlobalScope
|
import kotlinx.coroutines.GlobalScope
|
||||||
import kotlinx.coroutines.launch
|
import kotlinx.coroutines.launch
|
||||||
|
import me.saket.bettermovementmethod.BetterLinkMovementMethod
|
||||||
|
|
||||||
class DetailAdapter(private val activity: Activity, private val lifecycleScope: CoroutineScope, private val repository: Repository, private val onClick: (Notification) -> Unit, private val onLongClick: (Notification) -> Unit) :
|
class DetailAdapter(private val activity: Activity, private val lifecycleScope: CoroutineScope, private val repository: Repository, private val onClick: (Notification) -> Unit, private val onLongClick: (Notification) -> Unit) :
|
||||||
ListAdapter<Notification, DetailAdapter.DetailViewHolder>(TopicDiffCallback) {
|
ListAdapter<Notification, DetailAdapter.DetailViewHolder>(TopicDiffCallback) {
|
||||||
|
private val markwon: Markwon = MarkwonFactory.createForMessage(activity)
|
||||||
val selected = mutableSetOf<String>() // Notification IDs
|
val selected = mutableSetOf<String>() // Notification IDs
|
||||||
|
|
||||||
/* Creates and inflates view and return TopicViewHolder. */
|
/* Creates and inflates view and return TopicViewHolder. */
|
||||||
override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): DetailViewHolder {
|
override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): DetailViewHolder {
|
||||||
val view = LayoutInflater.from(parent.context)
|
val view = LayoutInflater.from(parent.context)
|
||||||
.inflate(R.layout.fragment_detail_item, parent, false)
|
.inflate(R.layout.fragment_detail_item, parent, false)
|
||||||
return DetailViewHolder(activity, lifecycleScope, repository, view, selected, onClick, onLongClick)
|
return DetailViewHolder(activity, lifecycleScope, repository, markwon, view, selected, onClick, onLongClick)
|
||||||
}
|
}
|
||||||
|
|
||||||
/* Gets current topic and uses it to bind view. */
|
/* Gets current topic and uses it to bind view. */
|
||||||
|
@ -73,7 +78,16 @@ class DetailAdapter(private val activity: Activity, private val lifecycleScope:
|
||||||
}
|
}
|
||||||
|
|
||||||
/* ViewHolder for Topic, takes in the inflated view and the onClick behavior. */
|
/* ViewHolder for Topic, takes in the inflated view and the onClick behavior. */
|
||||||
class DetailViewHolder(private val activity: Activity, private val lifecycleScope: CoroutineScope, private val repository: Repository, itemView: View, private val selected: Set<String>, val onClick: (Notification) -> Unit, val onLongClick: (Notification) -> Unit) :
|
class DetailViewHolder(
|
||||||
|
private val activity: Activity,
|
||||||
|
private val lifecycleScope: CoroutineScope,
|
||||||
|
private val repository: Repository,
|
||||||
|
private val markwon: Markwon,
|
||||||
|
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 layout: View = itemView.findViewById(R.id.detail_item_layout)
|
private val layout: View = itemView.findViewById(R.id.detail_item_layout)
|
||||||
|
@ -98,9 +112,17 @@ class DetailAdapter(private val activity: Activity, private val lifecycleScope:
|
||||||
|
|
||||||
val context = itemView.context
|
val context = itemView.context
|
||||||
val unmatchedTags = unmatchedTags(splitTags(notification.tags))
|
val unmatchedTags = unmatchedTags(splitTags(notification.tags))
|
||||||
|
val message = maybeAppendActionErrors(formatMessage(notification), notification)
|
||||||
|
|
||||||
dateView.text = formatDateShort(notification.timestamp)
|
dateView.text = formatDateShort(notification.timestamp)
|
||||||
messageView.text = maybeAppendActionErrors(formatMessage(notification), notification)
|
if (notification.isMarkdown()) {
|
||||||
|
messageView.autoLinkMask = 0
|
||||||
|
markwon.setMarkdown(messageView, message.toString())
|
||||||
|
} else {
|
||||||
|
messageView.autoLinkMask = Linkify.WEB_URLS
|
||||||
|
messageView.text = message
|
||||||
|
}
|
||||||
|
messageView.movementMethod = BetterLinkMovementMethod.getInstance()
|
||||||
messageView.setOnClickListener {
|
messageView.setOnClickListener {
|
||||||
// Click & Long-click listeners on the text as well, because "autoLink=web" makes them
|
// Click & Long-click listeners on the text as well, because "autoLink=web" makes them
|
||||||
// clickable, and so we cannot rely on the underlying card to perform the action.
|
// clickable, and so we cannot rely on the underlying card to perform the action.
|
||||||
|
@ -143,6 +165,13 @@ class DetailAdapter(private val activity: Activity, private val lifecycleScope:
|
||||||
maybeRenderActions(context, notification)
|
maybeRenderActions(context, notification)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private fun maybeMarkdown(message: String, notification: Notification): CharSequence {
|
||||||
|
if (notification.isMarkdown()) {
|
||||||
|
return markwon.toMarkdown(message)
|
||||||
|
}
|
||||||
|
return message
|
||||||
|
}
|
||||||
|
|
||||||
private fun renderPriority(context: Context, notification: Notification) {
|
private fun renderPriority(context: Context, notification: Notification) {
|
||||||
when (notification.priority) {
|
when (notification.priority) {
|
||||||
PRIORITY_MIN -> {
|
PRIORITY_MIN -> {
|
||||||
|
|
116
app/src/main/java/io/heckel/ntfy/util/MarkwonFactory.kt
Normal file
116
app/src/main/java/io/heckel/ntfy/util/MarkwonFactory.kt
Normal file
|
@ -0,0 +1,116 @@
|
||||||
|
package io.heckel.ntfy.util
|
||||||
|
|
||||||
|
import android.content.Context
|
||||||
|
import android.graphics.Color
|
||||||
|
import android.graphics.Typeface
|
||||||
|
import android.text.style.*
|
||||||
|
import android.text.util.Linkify
|
||||||
|
import androidx.core.content.ContextCompat
|
||||||
|
import io.heckel.ntfy.R
|
||||||
|
import io.noties.markwon.*
|
||||||
|
import io.noties.markwon.core.CorePlugin
|
||||||
|
import io.noties.markwon.core.CoreProps
|
||||||
|
import io.noties.markwon.core.MarkwonTheme
|
||||||
|
import io.noties.markwon.core.spans.LinkSpan
|
||||||
|
import io.noties.markwon.ext.strikethrough.StrikethroughPlugin
|
||||||
|
import io.noties.markwon.image.ImagesPlugin
|
||||||
|
import io.noties.markwon.linkify.LinkifyPlugin
|
||||||
|
import io.noties.markwon.movement.MovementMethodPlugin
|
||||||
|
import me.saket.bettermovementmethod.BetterLinkMovementMethod
|
||||||
|
import org.commonmark.ext.gfm.tables.TableCell
|
||||||
|
import org.commonmark.ext.gfm.tables.TablesExtension
|
||||||
|
import org.commonmark.node.*
|
||||||
|
import org.commonmark.parser.Parser
|
||||||
|
|
||||||
|
internal object MarkwonFactory {
|
||||||
|
fun createForMessage(context: Context): Markwon {
|
||||||
|
val headingSizes = floatArrayOf(1.7f, 1.5f, 1.2f, 1f, .8f, .7f)
|
||||||
|
val bulletGapWidth = (8 * context.resources.displayMetrics.density + 0.5f).toInt()
|
||||||
|
|
||||||
|
return Markwon.builder(context)
|
||||||
|
.usePlugin(CorePlugin.create())
|
||||||
|
.usePlugin(MovementMethodPlugin.create(BetterLinkMovementMethod.getInstance()))
|
||||||
|
.usePlugin(ImagesPlugin.create())
|
||||||
|
.usePlugin(LinkifyPlugin.create(Linkify.WEB_URLS))
|
||||||
|
.usePlugin(StrikethroughPlugin.create())
|
||||||
|
.usePlugin(object : AbstractMarkwonPlugin() {
|
||||||
|
override fun configureTheme(builder: MarkwonTheme.Builder) {
|
||||||
|
builder
|
||||||
|
.linkColor(ContextCompat.getColor(context, R.color.teal))
|
||||||
|
.isLinkUnderlined(true)
|
||||||
|
.blockMargin(0)
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun configureConfiguration(builder: MarkwonConfiguration.Builder) {
|
||||||
|
builder
|
||||||
|
.linkResolver(LinkResolverDef())
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun configureSpansFactory(builder: MarkwonSpansFactory.Builder) {
|
||||||
|
builder
|
||||||
|
.setFactory(Heading::class.java) { _, props: RenderProps? ->
|
||||||
|
arrayOf<Any>(
|
||||||
|
RelativeSizeSpan(headingSizes[CoreProps.HEADING_LEVEL.require(props!!) - 1]),
|
||||||
|
StyleSpan(Typeface.BOLD)
|
||||||
|
)
|
||||||
|
}
|
||||||
|
.setFactory(Emphasis::class.java) { _, _ -> StyleSpan(Typeface.ITALIC) }
|
||||||
|
.setFactory(StrongEmphasis::class.java) { _, _ -> StyleSpan(Typeface.BOLD) }
|
||||||
|
.setFactory(BlockQuote::class.java) { _, _ -> QuoteSpan() }
|
||||||
|
.setFactory(Code::class.java) { _, _ ->
|
||||||
|
arrayOf<Any>(
|
||||||
|
BackgroundColorSpan(Color.LTGRAY),
|
||||||
|
//TypefaceSpan("monospace")
|
||||||
|
)
|
||||||
|
}
|
||||||
|
.setFactory(ListItem::class.java) { _, _ -> BulletSpan(bulletGapWidth) }
|
||||||
|
}
|
||||||
|
|
||||||
|
})
|
||||||
|
.build()
|
||||||
|
}
|
||||||
|
|
||||||
|
fun createForNotification(context: Context): Markwon {
|
||||||
|
val headingSizes = floatArrayOf(2f, 1.5f, 1.17f, 1f, .83f, .67f)
|
||||||
|
val bulletGapWidth = (8 * context.resources.displayMetrics.density + 0.5f).toInt()
|
||||||
|
|
||||||
|
return Markwon.builder(context)
|
||||||
|
.usePlugin(CorePlugin.create())
|
||||||
|
//.usePlugin(PicassoImagesPlugin.create(picasso))
|
||||||
|
.usePlugin(StrikethroughPlugin.create())
|
||||||
|
.usePlugin(object : AbstractMarkwonPlugin() {
|
||||||
|
override fun configureSpansFactory(builder: MarkwonSpansFactory.Builder) {
|
||||||
|
builder
|
||||||
|
.setFactory(Heading::class.java) { _, props: RenderProps? ->
|
||||||
|
arrayOf<Any>(
|
||||||
|
RelativeSizeSpan(headingSizes[CoreProps.HEADING_LEVEL.require(props!!) - 1]),
|
||||||
|
StyleSpan(Typeface.BOLD)
|
||||||
|
)
|
||||||
|
}
|
||||||
|
.setFactory(Emphasis::class.java) { _, _ -> StyleSpan(Typeface.ITALIC) }
|
||||||
|
.setFactory(StrongEmphasis::class.java) { _, _ -> StyleSpan(Typeface.BOLD) }
|
||||||
|
.setFactory(BlockQuote::class.java) { _, _ -> QuoteSpan() }
|
||||||
|
.setFactory(Code::class.java) { _, _ ->
|
||||||
|
arrayOf<Any>(
|
||||||
|
BackgroundColorSpan(Color.LTGRAY),
|
||||||
|
TypefaceSpan("monospace")
|
||||||
|
)
|
||||||
|
}
|
||||||
|
.setFactory(ListItem::class.java) { _, _ -> BulletSpan(bulletGapWidth) }
|
||||||
|
.setFactory(Link::class.java) { _, _ -> null }
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun configureParser(builder: Parser.Builder) {
|
||||||
|
builder.extensions(setOf(TablesExtension.create()))
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun configureVisitor(builder: MarkwonVisitor.Builder) {
|
||||||
|
builder.on(TableCell::class.java) { visitor: MarkwonVisitor, node: TableCell? ->
|
||||||
|
visitor.visitChildren(node!!)
|
||||||
|
visitor.builder().append(' ')
|
||||||
|
}
|
||||||
|
}
|
||||||
|
})
|
||||||
|
.build()
|
||||||
|
}
|
||||||
|
}
|
|
@ -212,7 +212,7 @@ fun formatActionLabel(action: Action): String {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
fun maybeAppendActionErrors(message: String, notification: Notification): String {
|
fun maybeAppendActionErrors(message: CharSequence, notification: Notification): CharSequence {
|
||||||
val actionErrors = notification.actions
|
val actionErrors = notification.actions
|
||||||
.orEmpty()
|
.orEmpty()
|
||||||
.mapNotNull { action -> action.error }
|
.mapNotNull { action -> action.error }
|
||||||
|
|
|
@ -92,6 +92,7 @@ class FirebaseService : FirebaseMessagingService() {
|
||||||
val click = data["click"]
|
val click = data["click"]
|
||||||
val iconUrl = data["icon"]
|
val iconUrl = data["icon"]
|
||||||
val actions = data["actions"] // JSON array as string, sigh ...
|
val actions = data["actions"] // JSON array as string, sigh ...
|
||||||
|
val contentType = data["content_type"]
|
||||||
val encoding = data["encoding"]
|
val encoding = data["encoding"]
|
||||||
val attachmentName = data["attachment_name"] ?: "attachment.bin"
|
val attachmentName = data["attachment_name"] ?: "attachment.bin"
|
||||||
val attachmentType = data["attachment_type"]
|
val attachmentType = data["attachment_type"]
|
||||||
|
@ -132,6 +133,7 @@ class FirebaseService : FirebaseMessagingService() {
|
||||||
timestamp = timestamp,
|
timestamp = timestamp,
|
||||||
title = title ?: "",
|
title = title ?: "",
|
||||||
message = message,
|
message = message,
|
||||||
|
contentType = contentType ?: "",
|
||||||
encoding = encoding ?: "",
|
encoding = encoding ?: "",
|
||||||
priority = toPriority(priority),
|
priority = toPriority(priority),
|
||||||
tags = tags ?: "",
|
tags = tags ?: "",
|
||||||
|
|
265
assets/logo_with_text.svg
Normal file
265
assets/logo_with_text.svg
Normal file
|
@ -0,0 +1,265 @@
|
||||||
|
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
|
||||||
|
<!-- Created with Inkscape (http://www.inkscape.org/) -->
|
||||||
|
|
||||||
|
<svg
|
||||||
|
width="50mm"
|
||||||
|
height="50mm"
|
||||||
|
viewBox="0 0 50 50"
|
||||||
|
version="1.1"
|
||||||
|
id="svg8"
|
||||||
|
inkscape:version="1.1.2 (0a00cf5339, 2022-02-04)"
|
||||||
|
sodipodi:docname="logo_with_text.svg"
|
||||||
|
inkscape:export-filename="/home/pheckel/Code/ntfy-android/assets/appstore_ios.png"
|
||||||
|
inkscape:export-xdpi="520.19202"
|
||||||
|
inkscape:export-ydpi="520.19202"
|
||||||
|
xmlns:inkscape="http://www.inkscape.org/namespaces/inkscape"
|
||||||
|
xmlns:sodipodi="http://sodipodi.sourceforge.net/DTD/sodipodi-0.dtd"
|
||||||
|
xmlns:xlink="http://www.w3.org/1999/xlink"
|
||||||
|
xmlns="http://www.w3.org/2000/svg"
|
||||||
|
xmlns:svg="http://www.w3.org/2000/svg"
|
||||||
|
xmlns:rdf="http://www.w3.org/1999/02/22-rdf-syntax-ns#"
|
||||||
|
xmlns:cc="http://creativecommons.org/ns#"
|
||||||
|
xmlns:dc="http://purl.org/dc/elements/1.1/">
|
||||||
|
<defs
|
||||||
|
id="defs2">
|
||||||
|
<linearGradient
|
||||||
|
inkscape:collect="always"
|
||||||
|
id="linearGradient4714">
|
||||||
|
<stop
|
||||||
|
style="stop-color:#348878;stop-opacity:1"
|
||||||
|
offset="0"
|
||||||
|
id="stop4710" />
|
||||||
|
<stop
|
||||||
|
style="stop-color:#52bca6;stop-opacity:1"
|
||||||
|
offset="1"
|
||||||
|
id="stop4712" />
|
||||||
|
</linearGradient>
|
||||||
|
<linearGradient
|
||||||
|
inkscape:collect="always"
|
||||||
|
id="linearGradient28858-5">
|
||||||
|
<stop
|
||||||
|
style="stop-color:#348878;stop-opacity:1"
|
||||||
|
offset="0"
|
||||||
|
id="stop28854-3" />
|
||||||
|
<stop
|
||||||
|
style="stop-color:#56bda8;stop-opacity:1"
|
||||||
|
offset="1"
|
||||||
|
id="stop28856-5" />
|
||||||
|
</linearGradient>
|
||||||
|
<linearGradient
|
||||||
|
inkscape:collect="always"
|
||||||
|
xlink:href="#linearGradient28858-5"
|
||||||
|
id="linearGradient3255"
|
||||||
|
x1="160.72209"
|
||||||
|
y1="128.53317"
|
||||||
|
x2="168.41153"
|
||||||
|
y2="134.32626"
|
||||||
|
gradientUnits="userSpaceOnUse"
|
||||||
|
gradientTransform="matrix(1.741513,0,0,1.741513,-224.24539,-136.36679)" />
|
||||||
|
<linearGradient
|
||||||
|
inkscape:collect="always"
|
||||||
|
xlink:href="#linearGradient4714"
|
||||||
|
id="linearGradient4633"
|
||||||
|
x1="0.034492966"
|
||||||
|
y1="-0.0003150744"
|
||||||
|
x2="50.319355"
|
||||||
|
y2="50.284546"
|
||||||
|
gradientUnits="userSpaceOnUse"
|
||||||
|
gradientTransform="matrix(0.46182346,0,0,0.46182346,-0.01592966,-1.6956565e-4)" />
|
||||||
|
<filter
|
||||||
|
style="color-interpolation-filters:sRGB;"
|
||||||
|
inkscape:label="Drop Shadow"
|
||||||
|
id="filter3958"
|
||||||
|
x="-0.076083149"
|
||||||
|
y="-0.091641662"
|
||||||
|
width="1.1759423"
|
||||||
|
height="1.2114791">
|
||||||
|
<feFlood
|
||||||
|
flood-opacity="0.192157"
|
||||||
|
flood-color="rgb(0,0,0)"
|
||||||
|
result="flood"
|
||||||
|
id="feFlood3948" />
|
||||||
|
<feComposite
|
||||||
|
in="flood"
|
||||||
|
in2="SourceGraphic"
|
||||||
|
operator="in"
|
||||||
|
result="composite1"
|
||||||
|
id="feComposite3950" />
|
||||||
|
<feGaussianBlur
|
||||||
|
in="composite1"
|
||||||
|
stdDeviation="4"
|
||||||
|
result="blur"
|
||||||
|
id="feGaussianBlur3952" />
|
||||||
|
<feOffset
|
||||||
|
dx="3"
|
||||||
|
dy="2.95367"
|
||||||
|
result="offset"
|
||||||
|
id="feOffset3954" />
|
||||||
|
<feComposite
|
||||||
|
in="SourceGraphic"
|
||||||
|
in2="offset"
|
||||||
|
operator="over"
|
||||||
|
result="composite2"
|
||||||
|
id="feComposite3956" />
|
||||||
|
</filter>
|
||||||
|
</defs>
|
||||||
|
<sodipodi:namedview
|
||||||
|
id="base"
|
||||||
|
pagecolor="#ffffff"
|
||||||
|
bordercolor="#666666"
|
||||||
|
borderopacity="1.0"
|
||||||
|
inkscape:pageopacity="0.0"
|
||||||
|
inkscape:pageshadow="2"
|
||||||
|
inkscape:zoom="4.4713302"
|
||||||
|
inkscape:cx="74.139011"
|
||||||
|
inkscape:cy="89.123366"
|
||||||
|
inkscape:document-units="mm"
|
||||||
|
inkscape:current-layer="layer1"
|
||||||
|
showgrid="false"
|
||||||
|
inkscape:measure-start="0,0"
|
||||||
|
inkscape:measure-end="0,0"
|
||||||
|
inkscape:snap-text-baseline="true"
|
||||||
|
inkscape:window-width="1846"
|
||||||
|
inkscape:window-height="1016"
|
||||||
|
inkscape:window-x="74"
|
||||||
|
inkscape:window-y="27"
|
||||||
|
inkscape:window-maximized="1"
|
||||||
|
fit-margin-top="0"
|
||||||
|
fit-margin-left="0"
|
||||||
|
fit-margin-right="0"
|
||||||
|
fit-margin-bottom="0"
|
||||||
|
showguides="false"
|
||||||
|
inkscape:guide-bbox="true"
|
||||||
|
inkscape:pagecheckerboard="0">
|
||||||
|
<sodipodi:guide
|
||||||
|
position="10.173514,67.718331"
|
||||||
|
orientation="1,0"
|
||||||
|
id="guide1770" />
|
||||||
|
<sodipodi:guide
|
||||||
|
position="39.965574,62.077508"
|
||||||
|
orientation="1,0"
|
||||||
|
id="guide1772" />
|
||||||
|
<sodipodi:guide
|
||||||
|
position="10.173514,39.789015"
|
||||||
|
orientation="0,-1"
|
||||||
|
id="guide1774" />
|
||||||
|
<sodipodi:guide
|
||||||
|
position="-2.3077334,9.9462015"
|
||||||
|
orientation="0,-1"
|
||||||
|
id="guide1776" />
|
||||||
|
<sodipodi:guide
|
||||||
|
position="14.990626,36.198285"
|
||||||
|
orientation="1,0"
|
||||||
|
id="guide4020" />
|
||||||
|
<sodipodi:guide
|
||||||
|
position="34.930725,39.789015"
|
||||||
|
orientation="1,0"
|
||||||
|
id="guide4022" />
|
||||||
|
<sodipodi:guide
|
||||||
|
position="12.7026,32.00465"
|
||||||
|
orientation="0,-1"
|
||||||
|
id="guide4024" />
|
||||||
|
<sodipodi:guide
|
||||||
|
position="11.377711,17.981227"
|
||||||
|
orientation="0,-1"
|
||||||
|
id="guide4026" />
|
||||||
|
</sodipodi:namedview>
|
||||||
|
<metadata
|
||||||
|
id="metadata5">
|
||||||
|
<rdf:RDF>
|
||||||
|
<cc:Work
|
||||||
|
rdf:about="">
|
||||||
|
<dc:format>image/svg+xml</dc:format>
|
||||||
|
<dc:type
|
||||||
|
rdf:resource="http://purl.org/dc/dcmitype/StillImage" />
|
||||||
|
</cc:Work>
|
||||||
|
</rdf:RDF>
|
||||||
|
</metadata>
|
||||||
|
<g
|
||||||
|
inkscape:groupmode="layer"
|
||||||
|
id="layer2"
|
||||||
|
inkscape:label="background"
|
||||||
|
style="display:inline">
|
||||||
|
<rect
|
||||||
|
style="fill:url(#linearGradient4633);fill-opacity:1;stroke:none;stroke-width:0.133067;stroke-linejoin:bevel"
|
||||||
|
id="rect4545"
|
||||||
|
width="23.222729"
|
||||||
|
height="23.222729"
|
||||||
|
x="0"
|
||||||
|
y="-0.0003150744" />
|
||||||
|
</g>
|
||||||
|
<g
|
||||||
|
inkscape:groupmode="layer"
|
||||||
|
id="layer5"
|
||||||
|
inkscape:label="drop shadow"
|
||||||
|
style="display:inline">
|
||||||
|
<path
|
||||||
|
id="path3646"
|
||||||
|
style="color:#000000;display:inline;fill:#ffffff;stroke:none;stroke-width:1.93113;-inkscape-stroke:none;filter:url(#filter3958)"
|
||||||
|
d="m 50.400391,46.882812 c -9.16879,0 -17.023438,7.2146 -17.023438,16.386719 v 0.0078 l 0.08984,71.369139 -2.302735,16.99219 31.3125,-8.31836 h 77.841802 c 9.16877,0 17.02344,-7.22425 17.02344,-16.39648 V 63.269531 c 0,-9.169496 -7.85031,-16.382463 -17.01563,-16.386719 h -0.008 z m 0,11.566407 h 89.917969 0.008 c 3.22151,0.0033 5.44922,2.346918 5.44922,4.820312 v 63.654299 c 0,2.47551 -2.23164,4.82031 -5.45703,4.82031 H 60.779297 l -15.908203,4.80664 0.162109,-0.9375 -0.08789,-72.343749 c 0,-2.475337 2.229739,-4.820312 5.455078,-4.820312 z"
|
||||||
|
transform="matrix(0.12288694,0,0,0.12288694,0,-1.6873665e-4)" />
|
||||||
|
</g>
|
||||||
|
<g
|
||||||
|
inkscape:label="foreground"
|
||||||
|
inkscape:groupmode="layer"
|
||||||
|
id="layer1"
|
||||||
|
transform="translate(-51.147327,-81.515579)"
|
||||||
|
style="display:inline">
|
||||||
|
<path
|
||||||
|
style="color:#000000;fill:url(#linearGradient3255);stroke:none;stroke-width:1.15908;-inkscape-stroke:none"
|
||||||
|
d="M 68.356939,87.921737 H 57.543468 c -0.743432,0 -1.351677,0.574055 -1.351677,1.275675 l 0.01033,8.639597 -0.201904,1.163784 2.894292,-0.873723 h 9.46176 c 0.743433,0 1.351694,-0.574052 1.351694,-1.275689 v -7.653969 c 0,-0.70162 -0.608261,-1.275675 -1.351694,-1.275675 z"
|
||||||
|
id="path7368" />
|
||||||
|
<path
|
||||||
|
id="path2498"
|
||||||
|
style="color:#000000;fill:#ffffff;stroke:none;stroke-width:0.237311;-inkscape-stroke:none"
|
||||||
|
d="m 57.340877,87.276696 c -1.126725,0 -2.091958,0.88658 -2.091958,2.013713 v 9.59e-4 l 0.01104,8.770335 -0.282976,2.088117 3.847897,-1.022216 h 9.565741 c 1.126722,0 2.091958,-0.887766 2.091958,-2.014914 v -7.822281 c 0,-1.126811 -0.9647,-2.01319 -2.090998,-2.013713 h -9.83e-4 z m 0,1.42136 h 11.049744 9.83e-4 c 0.395882,4.06e-4 0.669638,0.288406 0.669638,0.592353 v 7.822282 c 0,0.304208 -0.274239,0.592354 -0.670598,0.592354 h -9.774335 l -1.954911,0.590673 0.01992,-0.115207 -0.0108,-8.890102 c 0,-0.304186 0.274006,-0.592353 0.670359,-0.592353 z" />
|
||||||
|
<g
|
||||||
|
id="path1011-6-2"
|
||||||
|
transform="matrix(0.67515919,0,0,0.80264534,16.256543,1.6511734)"
|
||||||
|
style="font-size:8.48274px;font-family:sans-serif;letter-spacing:0px;word-spacing:0px;fill:#000000;stroke:none;stroke-width:0.525121">
|
||||||
|
<path
|
||||||
|
style="color:#000000;-inkscape-font-specification:'JetBrains Mono, Bold';fill:#ffffff;stroke:none;-inkscape-stroke:none"
|
||||||
|
d="m 62.57046,116.77004 v -1.31201 l 3.280018,-1.45904 q 0.158346,-0.0679 0.305381,-0.1018 0.158346,-0.0452 0.282761,-0.0679 0.135725,-0.0113 0.271449,-0.0226 v -0.0905 q -0.135724,-0.0113 -0.271449,-0.0452 -0.124415,-0.0226 -0.282761,-0.0566 -0.147035,-0.0452 -0.305381,-0.1131 l -3.280018,-1.45904 v -1.32332 l 5.067063,2.31863 v 1.4138 z"
|
||||||
|
id="path7553" />
|
||||||
|
<path
|
||||||
|
style="color:#000000;-inkscape-font-specification:'JetBrains Mono, Bold';fill:#ffffff;stroke:none;-inkscape-stroke:none"
|
||||||
|
d="m 62.308594,110.31055 v 1.90234 l 3.4375,1.5293 c 0.0073,0.003 0.0142,0.005 0.02148,0.008 -0.0073,0.003 -0.0142,0.005 -0.02148,0.008 l -3.4375,1.5293 v 1.89258 l 0.371093,-0.16992 5.220704,-2.39063 v -1.75 z m 0.52539,0.8164 4.541016,2.08008 v 1.07617 l -4.541016,2.07813 v -0.73242 l 3.119141,-1.38868 0.0039,-0.002 c 0.09141,-0.0389 0.178343,-0.0676 0.257813,-0.0859 h 0.0059 l 0.0078,-0.002 c 0.09483,-0.0271 0.176055,-0.0474 0.246093,-0.0606 l 0.498047,-0.041 v -0.57422 l -0.240234,-0.0195 c -0.07606,-0.006 -0.153294,-0.0198 -0.230469,-0.0391 l -0.0078,-0.002 -0.0078,-0.002 c -0.07608,-0.0138 -0.16556,-0.0318 -0.263672,-0.0527 -0.08398,-0.0262 -0.172736,-0.058 -0.265625,-0.0977 l -0.0039,-0.002 -3.119141,-1.38868 z"
|
||||||
|
id="path7555" />
|
||||||
|
</g>
|
||||||
|
<g
|
||||||
|
id="g1224"
|
||||||
|
transform="matrix(0.67315851,0,0,0.77291871,16.72925,3.9953612)"
|
||||||
|
style="font-size:8.48274px;font-family:sans-serif;letter-spacing:0px;word-spacing:0px;fill:#000000;stroke:none;stroke-width:0.525121">
|
||||||
|
<path
|
||||||
|
style="color:#000000;-inkscape-font-specification:'JetBrains Mono, Bold';fill:#ffffff;stroke:none;-inkscape-stroke:none"
|
||||||
|
d="m 69.17132,117.75404 h 5.428996 v 1.27808 H 69.17132 Z"
|
||||||
|
id="path1220" />
|
||||||
|
<path
|
||||||
|
style="color:#000000;-inkscape-font-specification:'JetBrains Mono, Bold';fill:#ffffff;stroke:none;-inkscape-stroke:none"
|
||||||
|
d="m 68.908203,117.49219 v 0.26172 1.54101 h 5.955078 v -1.80273 z m 0.525391,0.52344 h 4.904297 v 0.7539 h -4.904297 z"
|
||||||
|
id="path1222" />
|
||||||
|
</g>
|
||||||
|
<text
|
||||||
|
xml:space="preserve"
|
||||||
|
style="font-style:normal;font-weight:normal;font-size:10.5833px;line-height:1.25;font-family:sans-serif;fill:#000000;fill-opacity:1;stroke:none;stroke-width:0.264583"
|
||||||
|
x="75.847778"
|
||||||
|
y="94.99572"
|
||||||
|
id="text7539"><tspan
|
||||||
|
sodipodi:role="line"
|
||||||
|
id="tspan7537"
|
||||||
|
style="font-style:normal;font-variant:normal;font-weight:900;font-stretch:normal;font-family:Inter;-inkscape-font-specification:'Inter Heavy';stroke-width:0.264583"
|
||||||
|
x="75.847778"
|
||||||
|
y="94.99572">ntfy.sh</tspan></text>
|
||||||
|
</g>
|
||||||
|
<g
|
||||||
|
inkscape:groupmode="layer"
|
||||||
|
id="layer3"
|
||||||
|
inkscape:label="round icon preview"
|
||||||
|
style="display:none">
|
||||||
|
<path
|
||||||
|
id="path18850-8-1"
|
||||||
|
style="display:inline;fill:#ffffff;fill-opacity:1;stroke-width:0.255654"
|
||||||
|
d="M 50.337488,80.973198 V 131.61213 H 101.65302 V 80.973198 Z m 25.676545,1.442307 h 0.555989 a 24.369387,24.369387 0 0 1 23.860308,21.232925 v 6.09963 a 24.369387,24.369387 0 0 1 -21.288308,21.19336 h 21.288308 v 0.0138 H 51.963792 v -0.0158 H 73.428179 A 24.369387,24.369387 0 0 1 51.963792,107.97535 v -2.49089 A 24.369387,24.369387 0 0 1 76.014033,82.415508 Z"
|
||||||
|
transform="translate(-51.147326,-81.51558)" />
|
||||||
|
</g>
|
||||||
|
</svg>
|
After Width: | Height: | Size: 11 KiB |
Loading…
Reference in a new issue