Compare commits

..

19 commits

Author SHA1 Message Date
Jonathan Klee
3fb7e6427f Merge branch '2585-u-change-package-name' into 'main'
Change Ntfy package name

See merge request e/os/ntfy-android!14
2024-10-24 11:26:08 +00:00
Jonathan Klee
afeebcc4d1 Change package name
So that in case the user had ntfy already installed, we don't
override it. Indeed, the Ntfy icon (which is actually a BlissLauncher3 cache)
would appear after the OTA and the user would not be able to remove it
without cleaning BlissLauncher3 cache.
2024-10-24 10:14:11 +02:00
Jonathan Klee
9add68ed1a Change preference wording 2024-10-10 14:42:34 +02:00
Jonathan Klee
18fb197507 Merge branch '0000-u-keep-same-process' into 'main'
Don't execute MainSettingsActivity in a different process

See merge request e/os/ntfy-android!13
2024-09-26 08:55:37 +00:00
Jonathan Klee
7c00376ae1 Don't execute MainSettingsActivity in a different process
so that share preferences work properly
2024-09-26 08:20:17 +02:00
Jonathan Klee
db299383c8 Use fdroid target 2024-09-25 09:04:23 +02:00
Jonathan Klee
e480230f27 Add gitlab yaml 2024-09-25 08:48:44 +02:00
Jonathan Klee
92611d1e81 Merge branch 'simpleran' into 'main'
Integrate ntfy in /e/OS

See merge request e/os/ntfy-android!12
2024-09-20 16:05:59 +00:00
Jonathan Klee
3b1581a2ba Set status bar theme programatically 2024-09-20 14:57:10 +02:00
Jonathan Klee
78bf83acf4 Extract isEnabled key as resource 2024-09-20 13:42:47 +02:00
Jonathan Klee
5684aba36d Implement NavigationBar theme 2024-09-20 13:31:20 +02:00
Jonathan Klee
61dadcba85 Add translations 2024-09-20 13:31:20 +02:00
Jonathan Klee
35711e7c1b Implement collapsing toolbar 2024-09-20 13:31:20 +02:00
Jonathan Klee
4f97fee55c Adjust styles 2024-09-20 13:31:15 +02:00
Jonathan Klee
fe86329af5 Add Preferences to enable Ntfy in the Settings app 2024-09-19 14:25:02 +02:00
Jonathan Klee
160c4cfd88 Don't show app on the launcher 2024-09-04 17:01:20 +02:00
Jonathan Klee
a4eb268aa0 Exclude app from Android recents app view 2024-09-04 17:00:31 +02:00
Jonathan Klee
95ab609d94 Make the app persistent so that it does not get killed 2024-09-04 16:59:53 +02:00
Jonathan Klee
5aceba6474 Start SubscriberService as background service 2024-09-04 16:59:36 +02:00
30 changed files with 301 additions and 504 deletions

21
.gitlab-ci.yml Normal file
View file

@ -0,0 +1,21 @@
image: "registry.gitlab.e.foundation/e/os/docker-android-apps-cicd:latest"
stages:
- build
before_script:
- export GRADLE_USER_HOME=$(pwd)/.gradle
- chmod +x ./gradlew
cache:
key: ${CI_PROJECT_ID}
paths:
- .gradle/
buildRelease:
stage: build
script:
- ./gradlew assembleFdroid
artifacts:
paths:
- app/build/outputs/apk/fdroid/release

View file

@ -10,8 +10,8 @@ android {
compileSdkVersion 33
defaultConfig {
applicationId "io.heckel.ntfy"
minSdkVersion 21
applicationId "foundation.e.ntfy"
minSdkVersion 23
targetSdkVersion 33
versionCode 33
@ -27,6 +27,10 @@ android {
}
}
buildFeatures {
viewBinding = true
}
buildTypes {
release {
minifyEnabled true
@ -36,8 +40,6 @@ android {
debug {
minifyEnabled false
debuggable true
applicationIdSuffix ".debug"
versionNameSuffix "-debug"
}
}
@ -66,6 +68,7 @@ android {
'-Xjvm-default=all-compatibility' // https://stackoverflow.com/a/71234042/1440785
]
}
namespace "io.heckel.ntfy"
}
// Disables GoogleServices tasks for F-Droid variant
@ -131,14 +134,5 @@ dependencies {
// Image viewer
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'
implementation 'foundation.e:elib:0.0.1-alpha11'
}

View file

@ -2,7 +2,7 @@
"formatVersion": 1,
"database": {
"version": 13,
"identityHash": "208f16743f21d9c374f1314878eb93cb",
"identityHash": "44fc291d937fdf02b9bc2d0abb10d2e0",
"entities": [
{
"tableName": "Subscription",
@ -94,10 +94,10 @@
}
],
"primaryKey": {
"autoGenerate": false,
"columnNames": [
"id"
]
],
"autoGenerate": false
},
"indices": [
{
@ -124,7 +124,7 @@
},
{
"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, `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`))",
"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`))",
"fields": [
{
"fieldPath": "id",
@ -156,12 +156,6 @@
"affinity": "TEXT",
"notNull": true
},
{
"fieldPath": "contentType",
"columnName": "contentType",
"affinity": "TEXT",
"notNull": true
},
{
"fieldPath": "encoding",
"columnName": "encoding",
@ -261,11 +255,11 @@
}
],
"primaryKey": {
"autoGenerate": false,
"columnNames": [
"id",
"subscriptionId"
]
],
"autoGenerate": false
},
"indices": [],
"foreignKeys": []
@ -294,10 +288,10 @@
}
],
"primaryKey": {
"autoGenerate": false,
"columnNames": [
"baseUrl"
]
],
"autoGenerate": false
},
"indices": [],
"foreignKeys": []
@ -344,10 +338,10 @@
}
],
"primaryKey": {
"autoGenerate": true,
"columnNames": [
"id"
]
],
"autoGenerate": true
},
"indices": [],
"foreignKeys": []
@ -356,7 +350,7 @@
"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, '208f16743f21d9c374f1314878eb93cb')"
"INSERT OR REPLACE INTO room_master_table (id,identity_hash) VALUES(42, '44fc291d937fdf02b9bc2d0abb10d2e0')"
]
}
}

View file

@ -1,4 +0,0 @@
<?xml version="1.0" encoding="utf-8"?>
<resources>
<string name="app_name" translatable="false">ntfy (debug)</string>
</resources>

View file

@ -1,6 +1,6 @@
<?xml version="1.0" encoding="utf-8"?>
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
package="io.heckel.ntfy">
xmlns:tools="http://schemas.android.com/tools">
<!-- Permissions -->
<uses-permission android:name="android.permission.INTERNET"/>
@ -25,6 +25,7 @@
android:allowBackup="true"
android:icon="@mipmap/ic_launcher"
android:label="@string/app_name"
android:persistent="true"
android:roundIcon="@mipmap/ic_launcher"
android:supportsRtl="true"
android:theme="@style/AppTheme"
@ -35,10 +36,10 @@
<activity
android:name=".ui.MainActivity"
android:label="@string/app_name"
android:excludeFromRecents="true"
android:exported="true">
<intent-filter>
<action android:name="android.intent.action.MAIN"/>
<category android:name="android.intent.category.LAUNCHER"/>
</intent-filter>
</activity>
@ -175,5 +176,25 @@
android:name="android.support.FILE_PROVIDER_PATHS"
android:resource="@xml/file_paths"/>
</provider>
<activity
android:name=".ui.MainSettingsActivity"
android:theme="@style/PreferenceTheme"/>
<activity-alias
android:name=".ui.SettingsActivityLink"
android:exported="true"
android:label="@string/eos_settings_title"
android:targetActivity=".ui.MainSettingsActivity">
<intent-filter>
<action android:name="com.android.settings.action.EXTRA_SETTINGS" />
</intent-filter>
<meta-data
android:name="com.android.settings.category"
android:value="com.android.settings.category.device" />
<meta-data
android:name="com.android.settings.icon"
android:resource="@drawable/ic_notification" />
</activity-alias>
</application>
</manifest>

View file

@ -183,7 +183,6 @@ class Backuper(val context: Context) {
timestamp = n.timestamp,
title = n.title,
message = n.message,
contentType = n.contentType,
encoding = n.encoding,
notificationId = 0,
priority = n.priority,
@ -313,7 +312,6 @@ class Backuper(val context: Context) {
timestamp = n.timestamp,
title = n.title,
message = n.message,
contentType = n.contentType,
encoding = n.encoding,
priority = n.priority,
tags = n.tags,
@ -388,7 +386,6 @@ data class Notification(
val timestamp: Long,
val title: String,
val message: String,
val contentType: String, // "" or "text/markdown" (empty assumes "text/plain")
val encoding: String, // "base64" or ""
val priority: Int, // 1=min, 3=default, 5=max
val tags: String,

View file

@ -99,7 +99,6 @@ data class Notification(
@ColumnInfo(name = "timestamp") val timestamp: Long, // Unix timestamp
@ColumnInfo(name = "title") val title: 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 = "notificationId") val notificationId: Int, // Android notification popup ID
@ColumnInfo(name = "priority", defaultValue = "3") val priority: Int, // 1=min, 3=default, 5=max
@ -111,10 +110,6 @@ data class Notification(
@ColumnInfo(name = "deleted") val deleted: Boolean,
)
fun Notification.isMarkdown(): Boolean {
return contentType == "text/markdown"
}
@Entity
data class Attachment(
@ColumnInfo(name = "name") val name: String, // Filename

View file

@ -28,7 +28,6 @@ class BroadcastService(private val ctx: Context) {
intent.putExtra("message", decodeMessage(notification))
intent.putExtra("message_bytes", decodeBytesMessage(notification))
intent.putExtra("message_encoding", notification.encoding)
intent.putExtra("content_type", notification.contentType)
intent.putExtra("tags", notification.tags)
intent.putExtra("tags_map", joinTagsMap(splitTags(notification.tags)))
intent.putExtra("priority", notification.priority)

View file

@ -1,7 +1,6 @@
package io.heckel.ntfy.msg
import androidx.annotation.Keep
import com.google.gson.annotations.SerializedName
/* This annotation ensures that proguard still works in production builds,
* see https://stackoverflow.com/a/62753300/1440785 */
@ -18,7 +17,6 @@ data class Message(
val actions: List<MessageAction>?,
val title: String?,
val message: String,
@SerializedName("content_type") val contentType: String?,
val encoding: String?,
val attachment: MessageAttachment?,
)

View file

@ -57,7 +57,6 @@ class NotificationParser {
timestamp = message.time,
title = message.title ?: "",
message = message.message,
contentType = message.contentType ?: "",
encoding = message.encoding ?: "",
priority = toPriority(message.priority),
tags = joinTags(message.tags),

View file

@ -11,8 +11,6 @@ import android.media.RingtoneManager
import android.net.Uri
import android.os.Build
import android.os.Bundle
import android.text.SpannedString
import android.text.style.CharacterStyle
import android.widget.Toast
import androidx.core.app.NotificationCompat
import androidx.core.content.ContextCompat
@ -28,7 +26,6 @@ import java.util.*
class NotificationService(val context: Context) {
private val notificationManager = context.getSystemService(Context.NOTIFICATION_SERVICE) as NotificationManager
private val repository = Repository.getInstance(context)
private val markwon = MarkwonFactory.createForNotification(context)
fun display(subscription: Subscription, notification: Notification) {
Log.d(TAG, "Displaying notification $notification")
@ -150,7 +147,7 @@ class NotificationService(val context: Context) {
try {
val attachmentBitmap = contentUri.readBitmapFromUri(context)
builder
.setContentText(maybeAppendActionErrors(maybeMarkdown(formatMessage(notification), notification), notification))
.setContentText(maybeAppendActionErrors(formatMessage(notification), notification))
.setLargeIcon(attachmentBitmap)
.setStyle(NotificationCompat.BigPictureStyle()
.bigPicture(attachmentBitmap)
@ -170,8 +167,8 @@ class NotificationService(val context: Context) {
}
}
private fun formatMessageMaybeWithAttachmentInfos(notification: Notification): CharSequence {
val message = maybeMarkdown(formatMessage(notification), notification)
private fun formatMessageMaybeWithAttachmentInfos(notification: Notification): String {
val message = formatMessage(notification)
val attachment = notification.attachment ?: return message
val attachmentInfos = if (attachment.size != null) {
"${attachment.name}, ${formatBytes(attachment.size)}"
@ -517,13 +514,6 @@ 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 {
const val ACTION_VIEW = "view"
const val ACTION_HTTP = "http"

View file

@ -10,6 +10,7 @@ import android.os.PowerManager
import android.os.SystemClock
import androidx.core.app.NotificationCompat
import androidx.core.content.ContextCompat
import androidx.preference.PreferenceManager
import io.heckel.ntfy.BuildConfig
import io.heckel.ntfy.R
import io.heckel.ntfy.app.Application
@ -88,23 +89,16 @@ class SubscriberService : Service() {
Log.init(this) // Init logs in all entry points
Log.d(TAG, "Subscriber service has been created")
val title = getString(R.string.channel_subscriber_notification_title)
val text = if (BuildConfig.FIREBASE_AVAILABLE) {
getString(R.string.channel_subscriber_notification_instant_text)
} else {
getString(R.string.channel_subscriber_notification_noinstant_text)
}
notificationManager = createNotificationChannel()
serviceNotification = createNotification(title, text)
startForeground(NOTIFICATION_SERVICE_ID, serviceNotification)
}
override fun onDestroy() {
Log.d(TAG, "Subscriber service has been destroyed")
stopService()
sendBroadcast(Intent(this, AutoRestartReceiver::class.java)) // Restart it if necessary!
val preferenceKey = getString(R.string.eos_preference_key_is_enabled)
if (PreferenceManager.getDefaultSharedPreferences(this).getBoolean(preferenceKey, false)) {
sendBroadcast(Intent(this, AutoRestartReceiver::class.java))
}
super.onDestroy()
}
@ -138,7 +132,6 @@ class SubscriberService : Service() {
}
}
wakeLock = null
stopForeground(true)
stopSelf()
} catch (e: Exception) {
Log.d(TAG, "Service stopped without being started: ${e.message}")

View file

@ -2,10 +2,11 @@ package io.heckel.ntfy.service
import android.content.Context
import android.content.Intent
import androidx.core.content.ContextCompat
import androidx.preference.PreferenceManager
import androidx.work.*
import io.heckel.ntfy.app.Application
import io.heckel.ntfy.util.Log
import io.heckel.ntfy.R
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.withContext
@ -43,11 +44,17 @@ class SubscriberServiceManager(private val context: Context) {
Log.d(TAG, "ServiceStartWorker: Failed, no application found (work ID: ${id})")
return Result.failure()
}
withContext(Dispatchers.IO) {
val app = context.applicationContext as Application
val subscriptionIdsWithInstantStatus = app.repository.getSubscriptionIdsWithInstantStatus()
val instantSubscriptions = subscriptionIdsWithInstantStatus.toList().filter { (_, instant) -> instant }.size
val action = if (instantSubscriptions > 0) SubscriberService.Action.START else SubscriberService.Action.STOP
val sharedPreferences = PreferenceManager.getDefaultSharedPreferences(app)
val preferenceKey = context.getString(R.string.eos_preference_key_is_enabled)
val action = if (sharedPreferences.getBoolean(preferenceKey, false)) {
SubscriberService.Action.START
} else {
SubscriberService.Action.STOP
}
val serviceState = SubscriberService.readServiceState(context)
if (serviceState == SubscriberService.ServiceState.STOPPED && action == SubscriberService.Action.STOP) {
return@withContext Result.success()
@ -55,7 +62,7 @@ class SubscriberServiceManager(private val context: Context) {
Log.d(TAG, "ServiceStartWorker: Starting foreground service with action $action (work ID: ${id})")
Intent(context, SubscriberService::class.java).also {
it.action = action.name
ContextCompat.startForegroundService(context, it)
context.startService(it)
}
}
return Result.success()

View file

@ -9,8 +9,6 @@ import android.net.Uri
import android.os.Build
import android.os.Environment
import android.provider.MediaStore
import android.text.method.LinkMovementMethod
import android.text.util.Linkify
import android.view.LayoutInflater
import android.view.View
import android.view.ViewGroup
@ -35,23 +33,20 @@ import io.heckel.ntfy.msg.DownloadType
import io.heckel.ntfy.msg.NotificationService
import io.heckel.ntfy.msg.NotificationService.Companion.ACTION_VIEW
import io.heckel.ntfy.util.*
import io.noties.markwon.Markwon
import kotlinx.coroutines.CoroutineScope
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.GlobalScope
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) :
ListAdapter<Notification, DetailAdapter.DetailViewHolder>(TopicDiffCallback) {
private val markwon: Markwon = MarkwonFactory.createForMessage(activity)
val selected = mutableSetOf<String>() // Notification IDs
/* Creates and inflates view and return TopicViewHolder. */
override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): DetailViewHolder {
val view = LayoutInflater.from(parent.context)
.inflate(R.layout.fragment_detail_item, parent, false)
return DetailViewHolder(activity, lifecycleScope, repository, markwon, view, selected, onClick, onLongClick)
return DetailViewHolder(activity, lifecycleScope, repository, view, selected, onClick, onLongClick)
}
/* Gets current topic and uses it to bind view. */
@ -78,16 +73,7 @@ class DetailAdapter(private val activity: Activity, private val lifecycleScope:
}
/* 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,
private val markwon: Markwon,
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, itemView: View, private val selected: Set<String>, val onClick: (Notification) -> Unit, val onLongClick: (Notification) -> Unit) :
RecyclerView.ViewHolder(itemView) {
private var notification: Notification? = null
private val layout: View = itemView.findViewById(R.id.detail_item_layout)
@ -112,17 +98,9 @@ class DetailAdapter(private val activity: Activity, private val lifecycleScope:
val context = itemView.context
val unmatchedTags = unmatchedTags(splitTags(notification.tags))
val message = maybeAppendActionErrors(formatMessage(notification), notification)
dateView.text = formatDateShort(notification.timestamp)
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.text = maybeAppendActionErrors(formatMessage(notification), notification)
messageView.setOnClickListener {
// 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.
@ -165,13 +143,6 @@ class DetailAdapter(private val activity: Activity, private val lifecycleScope:
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) {
when (notification.priority) {
PRIORITY_MIN -> {

View file

@ -0,0 +1,71 @@
package io.heckel.ntfy.ui
import android.content.res.Configuration
import android.os.Build
import android.os.Bundle
import android.view.WindowInsetsController
import androidx.annotation.RequiresApi
import androidx.appcompat.app.AppCompatActivity
import io.heckel.ntfy.R
import io.heckel.ntfy.databinding.MainSettingsActivityBinding
class MainSettingsActivity : AppCompatActivity() {
private lateinit var mBinding: MainSettingsActivityBinding
@RequiresApi(Build.VERSION_CODES.R)
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
mBinding = MainSettingsActivityBinding.inflate(layoutInflater)
setContentView(mBinding.root)
setupToolbar()
setSystemBarsAppearance()
showPreferencesFragment()
}
private fun setupToolbar() {
mBinding.toolbar.setNavigationOnClickListener {
onBackPressedDispatcher.onBackPressed()
}
}
@RequiresApi(Build.VERSION_CODES.R)
private fun setSystemBarsAppearance() {
val insetsController = window.insetsController ?: return
val isLightMode = isSystemInLightMode()
if (isLightMode) {
insetsController.setSystemBarsAppearance(
WindowInsetsController.APPEARANCE_LIGHT_STATUS_BARS,
WindowInsetsController.APPEARANCE_LIGHT_STATUS_BARS
)
insetsController.setSystemBarsAppearance(
WindowInsetsController.APPEARANCE_LIGHT_NAVIGATION_BARS,
WindowInsetsController.APPEARANCE_LIGHT_NAVIGATION_BARS
)
} else {
insetsController.setSystemBarsAppearance(
0,
WindowInsetsController.APPEARANCE_LIGHT_STATUS_BARS
)
insetsController.setSystemBarsAppearance(
0,
WindowInsetsController.APPEARANCE_LIGHT_NAVIGATION_BARS
)
}
}
private fun isSystemInLightMode(): Boolean {
val nightModeFlags = resources.configuration.uiMode and Configuration.UI_MODE_NIGHT_MASK
return nightModeFlags != Configuration.UI_MODE_NIGHT_YES
}
private fun showPreferencesFragment() {
supportFragmentManager
.beginTransaction()
.replace(R.id.fragment_container, PreferencesFragment())
.commit()
}
}

View file

@ -0,0 +1,37 @@
package io.heckel.ntfy.ui
import android.content.Context
import android.content.Intent
import android.os.Bundle
import android.view.Menu
import android.view.MenuInflater
import android.view.MenuItem
import android.widget.Toolbar
import androidx.preference.PreferenceFragmentCompat
import androidx.preference.SwitchPreferenceCompat
import io.heckel.ntfy.R
import io.heckel.ntfy.service.SubscriberService
import io.heckel.ntfy.util.Log
class PreferencesFragment : PreferenceFragmentCompat() {
override fun onCreatePreferences(savedInstanceState: Bundle?, rootKey: String?) {
setPreferencesFromResource(R.xml.settings_preferences, rootKey)
val preference: SwitchPreferenceCompat? =
findPreference(getString(R.string.eos_preference_key_is_enabled))
preference?.setOnPreferenceChangeListener { _, newValue ->
val isChecked = newValue as Boolean
val intent = Intent(context, SubscriberService::class.java)
intent.action = if (isChecked) {
SubscriberService.Action.START.name
} else {
SubscriberService.Action.STOP.name
}
requireContext().startService(intent)
true
}
}
}

View file

@ -1,116 +0,0 @@
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()
}
}

View file

@ -212,7 +212,7 @@ fun formatActionLabel(action: Action): String {
}
}
fun maybeAppendActionErrors(message: CharSequence, notification: Notification): CharSequence {
fun maybeAppendActionErrors(message: String, notification: Notification): String {
val actionErrors = notification.actions
.orEmpty()
.mapNotNull { action -> action.error }

View file

@ -1,31 +1,31 @@
<vector xmlns:android="http://schemas.android.com/apk/res/android"
android:width="50dp"
android:height="50dp"
android:width="22dp"
android:height="22dp"
android:viewportWidth="50"
android:viewportHeight="50">
<path
android:pathData="m7.8399,6.35c-3.58,0 -6.6469,2.817 -6.6469,6.3983v0.003l0.0351,27.8668 -0.8991,6.6347 12.2261,-3.248L42.9487,44.0049c3.58,0 6.6469,-2.8208 6.6469,-6.4022v-24.8545c0,-3.5803 -3.0652,-6.3967 -6.6438,-6.3983h-0.0031zM7.8399,10.8662h35.1088,0.0031c1.2579,0.0013 2.1277,0.9164 2.1277,1.8821v24.8544c0,0.9666 -0.8714,1.8821 -2.1307,1.8821L11.8924,39.4849l-6.2114,1.8768 0.0633,-0.366 -0.0343,-28.2473c0,-0.9665 0.8706,-1.8821 2.13,-1.8821z"
android:strokeWidth="0.754022"
android:fillColor="#FFFFFFFF"
android:fillColor="?android:attr/colorControlNormal"
android:strokeColor="#00000000"/>
<path
android:pathData="m11.5278,32.0849l0,-3.346l7.0363,-3.721q0.3397,-0.1732 0.6551,-0.2596 0.3397,-0.1153 0.6066,-0.1732 0.2912,-0.0288 0.5823,-0.0576l0,-0.2308q-0.2912,-0.0288 -0.5823,-0.1153 -0.2669,-0.0576 -0.6066,-0.1443 -0.3154,-0.1153 -0.6551,-0.2884l-7.0363,-3.721l0,-3.3749l10.8699,5.9132l0,3.6056z"
android:strokeWidth="0.525121"
android:fillColor="#FFFFFFFF"
android:fillColor="?android:attr/colorControlNormal"
android:strokeColor="#00000000"/>
<path
android:pathData="m10.9661,15.6112l0,4.8516l7.3742,3.9002c0.0157,0.0077 0.0305,0.0128 0.0461,0.0204 -0.0157,0.0077 -0.0305,0.0128 -0.0461,0.0204l-7.3742,3.9002l0,4.8267l0.7961,-0.4333 11.1995,-6.0969l0,-4.463zM12.0931,17.6933 L21.8346,22.9981l0,2.7446l-9.7414,5.2999l0,-1.8679l6.6912,-3.5416 0.0084,-0.0051c0.1961,-0.0992 0.3826,-0.1724 0.5531,-0.2191l0.0127,0l0.0167,-0.0051c0.2034,-0.0691 0.3777,-0.1209 0.5279,-0.1545l1.0684,-0.1046l0,-1.4644l-0.5154,-0.0497c-0.1632,-0.0153 -0.3288,-0.0505 -0.4944,-0.0997l-0.0167,-0.0051 -0.0167,-0.0051c-0.1632,-0.0352 -0.3552,-0.0811 -0.5656,-0.1344 -0.1802,-0.0668 -0.3706,-0.1479 -0.5698,-0.2492l-0.0084,-0.0051 -6.6912,-3.5416z"
android:strokeWidth="0.525121"
android:fillColor="#FFFFFFFF"
android:fillColor="?android:attr/colorControlNormal"
android:strokeColor="#00000000"/>
<path
android:pathData="m26.7503,30.9206l11.6118,0l0,3.1388L26.7503,34.0594Z"
android:strokeWidth="0.525121"
android:fillColor="#FFFFFFFF"
android:fillColor="?android:attr/colorControlNormal"
android:strokeColor="#00000000"/>
<path
android:pathData="m26.1875,30.2775l0,0.6427 0,3.7845l12.7371,0l0,-4.4272zM27.3113,31.563l10.4896,0l0,1.8515l-10.4896,0z"
android:strokeWidth="0.525121"
android:fillColor="#FFFFFFFF"
android:fillColor="?android:attr/colorControlNormal"
android:strokeColor="#00000000"/>
</vector>

View file

@ -0,0 +1,49 @@
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout
xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"
android:orientation="vertical"
android:fitsSystemWindows="true"
android:layout_width="match_parent"
android:layout_height="match_parent">
<com.google.android.material.appbar.AppBarLayout
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:fitsSystemWindows="true">
<com.google.android.material.appbar.CollapsingToolbarLayout
android:id="@+id/collapsing_toolbar"
style="?attr/collapsingToolbarLayoutLargeStyle"
android:background="@color/e_background"
android:layout_width="match_parent"
app:layout_scrollFlags="scroll|exitUntilCollapsed|snap"
android:layout_height="?attr/collapsingToolbarLayoutLargeSize">
<com.google.android.material.appbar.MaterialToolbar
app:navigationIcon="@drawable/e_ic_back"
app:title="@string/eos_settings_title"
android:id="@+id/toolbar"
android:background="@color/e_background"
android:layout_width="match_parent"
android:layout_height="?attr/actionBarSize"
app:layout_collapseMode="pin" />
</com.google.android.material.appbar.CollapsingToolbarLayout>
</com.google.android.material.appbar.AppBarLayout>
<androidx.core.widget.NestedScrollView
android:layout_width="match_parent"
android:layout_height="match_parent"
android:fillViewport="true"
app:layout_behavior="@string/appbar_scrolling_view_behavior">
<FrameLayout
android:id="@+id/fragment_container"
android:layout_width="match_parent"
android:layout_height="match_parent"/>
</androidx.core.widget.NestedScrollView>
</LinearLayout>

View file

@ -343,4 +343,6 @@
<string name="settings_advanced_unifiedpush_summary_disabled">ntfy arbeitet nicht als UnifiedPush-Distributor</string>
<string name="settings_advanced_unifiedpush_title">UnifiedPush aktivieren</string>
<string name="settings_advanced_unifiedpush_summary_enabled">ntfy arbeitet als UnifiedPush-Distributor</string>
<string name="eos_settings_enable_title">Den Verteiler aktivieren</string>
<string name="eos_settings_enable_description">Es ermöglicht Drittanbieteranwendungen, UnifiedPush-Benachrichtigungen zu empfangen</string>
</resources>

View file

@ -343,4 +343,6 @@
<string name="settings_advanced_unifiedpush_title">Activar UnifiedPush</string>
<string name="settings_advanced_unifiedpush_summary_enabled">ntfy actuará como distribuidor UnifiedPush</string>
<string name="settings_advanced_unifiedpush_summary_disabled">ntfy no actuará como distribuidor UnifiedPush</string>
<string name="eos_settings_enable_title">Habilitar el distribuidor</string>
<string name="eos_settings_enable_description">Permite a las aplicaciones de terceros recibir notificaciones de UnifiedPush</string>
</resources>

View file

@ -343,4 +343,6 @@
<string name="settings_advanced_unifiedpush_summary_enabled">ntfy agira comme un distributeur UnifiedPush</string>
<string name="settings_advanced_unifiedpush_summary_disabled">ntfy n\'agira pas comme un distributeur UnifiedPush</string>
<string name="settings_advanced_unifiedpush_title">Activer le \"UnifiedPush\"</string>
<string name="eos_settings_enable_title">Activer le distributeur</string>
<string name="eos_settings_enable_description">Cela permet aux applications tierces de recevoir des notifications UnifiedPush</string>
</resources>

View file

@ -330,4 +330,6 @@
<string name="channel_notifications_group_default_name">Default</string>
<string name="main_menu_donate_title">Dona 💸</string>
<string name="detail_item_cannot_open_apk">Le app non possono più essere installate: devono essere scaricate via browser. Vedi l\'issue #531 per dettagli.</string>
<string name="eos_settings_enable_title">Abilitare il distributore</string>
<string name="eos_settings_enable_description">Consente alle applicazioni di terze parti di ricevere notifiche UnifiedPush</string>
</resources>

View file

@ -391,4 +391,10 @@
<string name="user_dialog_button_cancel">Cancel</string>
<string name="user_dialog_button_delete">Delete user</string>
<string name="user_dialog_button_save">Save</string>
<!-- /e/OS integration preferences -->
<string name="eos_preference_key_is_enabled" translatable="false">isEnabled</string>
<string name="eos_settings_title" translatable="false">UnifiedPush</string>
<string name="eos_settings_enable_title" translatable="true">Enable the distributor</string>
<string name="eos_settings_enable_description" translatable="true">It allows 3rd party applications to receive UnifiedPush notifications</string>
</resources>

View file

@ -30,4 +30,26 @@
<item name="cornerFamily">rounded</item>
<item name="cornerSize">5dp</item>
</style>
<style name="PreferenceTheme" parent="PreferenceTheme.Base"/>
<style name="PreferenceTheme.Base" parent="Theme.Material3.DayNight.NoActionBar">
<item name="colorPrimary">@color/e_action_bar</item>
<item name="colorPrimaryDark">@color/e_action_bar</item>
<item name="colorAccent">@color/e_accent</item>
<item name="android:textColorPrimary">@color/e_primary_text_color</item>
<item name="android:textColorSecondary">@color/e_secondary_text_color</item>
<item name="android:textColorPrimaryInverse">@color/e_background</item>
<item name="android:windowBackground">@color/e_background</item>
<item name="colorControlActivated">@color/e_accent</item>
<item name="colorButtonNormal">@color/e_icon_color</item>
<item name="colorControlHighlight">@color/e_icon_color</item>
<item name="homeAsUpIndicator">@drawable/e_ic_back</item>
<item name="android:homeAsUpIndicator">@drawable/e_ic_back</item>
<item name="android:popupBackground">@color/e_floating_background</item>
<item name="android:divider">@color/e_divider_color</item>
<item name="android:navigationBarColor">@android:color/transparent</item>
<item name="android:statusBarColor">@color/e_background</item>
<item name="switchStyle">@style/ETheme.Switch</item>
</style>
</resources>

View file

@ -0,0 +1,11 @@
<PreferenceScreen
xmlns:app="http://schemas.android.com/apk/res-auto">
<SwitchPreferenceCompat
app:icon="@drawable/ic_notification"
app:key="@string/eos_preference_key_is_enabled"
app:defaultValue="false"
app:title="@string/eos_settings_enable_title"
app:summary="@string/eos_settings_enable_description" />
</PreferenceScreen>

View file

@ -92,7 +92,6 @@ class FirebaseService : FirebaseMessagingService() {
val click = data["click"]
val iconUrl = data["icon"]
val actions = data["actions"] // JSON array as string, sigh ...
val contentType = data["content_type"]
val encoding = data["encoding"]
val attachmentName = data["attachment_name"] ?: "attachment.bin"
val attachmentType = data["attachment_type"]
@ -133,7 +132,6 @@ class FirebaseService : FirebaseMessagingService() {
timestamp = timestamp,
title = title ?: "",
message = message,
contentType = contentType ?: "",
encoding = encoding ?: "",
priority = toPriority(priority),
tags = tags ?: "",

View file

@ -1,265 +0,0 @@
<?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>

Before

Width:  |  Height:  |  Size: 11 KiB

View file

@ -18,6 +18,7 @@ allprojects {
repositories {
google()
mavenCentral()
maven { url 'https://gitlab.e.foundation/api/v4/groups/9/-/packages/maven'}
maven { url "https://jitpack.io" } // For StfalconImageViewer
}
}