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 |
30 changed files with 504 additions and 301 deletions
|
@ -1,21 +0,0 @@
|
|||
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
|
|
@ -10,8 +10,8 @@ android {
|
|||
compileSdkVersion 33
|
||||
|
||||
defaultConfig {
|
||||
applicationId "foundation.e.ntfy"
|
||||
minSdkVersion 23
|
||||
applicationId "io.heckel.ntfy"
|
||||
minSdkVersion 21
|
||||
targetSdkVersion 33
|
||||
|
||||
versionCode 33
|
||||
|
@ -27,10 +27,6 @@ android {
|
|||
}
|
||||
}
|
||||
|
||||
buildFeatures {
|
||||
viewBinding = true
|
||||
}
|
||||
|
||||
buildTypes {
|
||||
release {
|
||||
minifyEnabled true
|
||||
|
@ -40,6 +36,8 @@ android {
|
|||
debug {
|
||||
minifyEnabled false
|
||||
debuggable true
|
||||
applicationIdSuffix ".debug"
|
||||
versionNameSuffix "-debug"
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -68,7 +66,6 @@ android {
|
|||
'-Xjvm-default=all-compatibility' // https://stackoverflow.com/a/71234042/1440785
|
||||
]
|
||||
}
|
||||
namespace "io.heckel.ntfy"
|
||||
}
|
||||
|
||||
// Disables GoogleServices tasks for F-Droid variant
|
||||
|
@ -134,5 +131,14 @@ dependencies {
|
|||
// Image viewer
|
||||
implementation 'com.github.stfalcon-studio:StfalconImageViewer:v1.0.1'
|
||||
|
||||
implementation 'foundation.e:elib:0.0.1-alpha11'
|
||||
// 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,
|
||||
"database": {
|
||||
"version": 13,
|
||||
"identityHash": "44fc291d937fdf02b9bc2d0abb10d2e0",
|
||||
"identityHash": "208f16743f21d9c374f1314878eb93cb",
|
||||
"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, `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": [
|
||||
{
|
||||
"fieldPath": "id",
|
||||
|
@ -156,6 +156,12 @@
|
|||
"affinity": "TEXT",
|
||||
"notNull": true
|
||||
},
|
||||
{
|
||||
"fieldPath": "contentType",
|
||||
"columnName": "contentType",
|
||||
"affinity": "TEXT",
|
||||
"notNull": true
|
||||
},
|
||||
{
|
||||
"fieldPath": "encoding",
|
||||
"columnName": "encoding",
|
||||
|
@ -255,11 +261,11 @@
|
|||
}
|
||||
],
|
||||
"primaryKey": {
|
||||
"autoGenerate": false,
|
||||
"columnNames": [
|
||||
"id",
|
||||
"subscriptionId"
|
||||
],
|
||||
"autoGenerate": false
|
||||
]
|
||||
},
|
||||
"indices": [],
|
||||
"foreignKeys": []
|
||||
|
@ -288,10 +294,10 @@
|
|||
}
|
||||
],
|
||||
"primaryKey": {
|
||||
"autoGenerate": false,
|
||||
"columnNames": [
|
||||
"baseUrl"
|
||||
],
|
||||
"autoGenerate": false
|
||||
]
|
||||
},
|
||||
"indices": [],
|
||||
"foreignKeys": []
|
||||
|
@ -338,10 +344,10 @@
|
|||
}
|
||||
],
|
||||
"primaryKey": {
|
||||
"autoGenerate": true,
|
||||
"columnNames": [
|
||||
"id"
|
||||
],
|
||||
"autoGenerate": true
|
||||
]
|
||||
},
|
||||
"indices": [],
|
||||
"foreignKeys": []
|
||||
|
@ -350,7 +356,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, '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>
|
|
@ -1,6 +1,6 @@
|
|||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
xmlns:tools="http://schemas.android.com/tools">
|
||||
package="io.heckel.ntfy">
|
||||
|
||||
<!-- Permissions -->
|
||||
<uses-permission android:name="android.permission.INTERNET"/>
|
||||
|
@ -25,7 +25,6 @@
|
|||
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"
|
||||
|
@ -36,10 +35,10 @@
|
|||
<activity
|
||||
android:name=".ui.MainActivity"
|
||||
android:label="@string/app_name"
|
||||
android:excludeFromRecents="true"
|
||||
android:exported="true">
|
||||
android:exported="true">
|
||||
<intent-filter>
|
||||
<action android:name="android.intent.action.MAIN"/>
|
||||
<category android:name="android.intent.category.LAUNCHER"/>
|
||||
</intent-filter>
|
||||
</activity>
|
||||
|
||||
|
@ -176,25 +175,5 @@
|
|||
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>
|
||||
|
|
|
@ -183,6 +183,7 @@ 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,
|
||||
|
@ -312,6 +313,7 @@ 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,
|
||||
|
@ -386,6 +388,7 @@ 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,
|
||||
|
|
|
@ -99,6 +99,7 @@ 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
|
||||
|
@ -110,6 +111,10 @@ 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
|
||||
|
|
|
@ -28,6 +28,7 @@ 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)
|
||||
|
|
|
@ -1,6 +1,7 @@
|
|||
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 */
|
||||
|
@ -17,6 +18,7 @@ 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?,
|
||||
)
|
||||
|
|
|
@ -57,6 +57,7 @@ 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),
|
||||
|
|
|
@ -11,6 +11,8 @@ 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
|
||||
|
@ -26,6 +28,7 @@ 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")
|
||||
|
@ -147,7 +150,7 @@ class NotificationService(val context: Context) {
|
|||
try {
|
||||
val attachmentBitmap = contentUri.readBitmapFromUri(context)
|
||||
builder
|
||||
.setContentText(maybeAppendActionErrors(formatMessage(notification), notification))
|
||||
.setContentText(maybeAppendActionErrors(maybeMarkdown(formatMessage(notification), notification), notification))
|
||||
.setLargeIcon(attachmentBitmap)
|
||||
.setStyle(NotificationCompat.BigPictureStyle()
|
||||
.bigPicture(attachmentBitmap)
|
||||
|
@ -167,8 +170,8 @@ class NotificationService(val context: Context) {
|
|||
}
|
||||
}
|
||||
|
||||
private fun formatMessageMaybeWithAttachmentInfos(notification: Notification): String {
|
||||
val message = formatMessage(notification)
|
||||
private fun formatMessageMaybeWithAttachmentInfos(notification: Notification): CharSequence {
|
||||
val message = maybeMarkdown(formatMessage(notification), notification)
|
||||
val attachment = notification.attachment ?: return message
|
||||
val attachmentInfos = if (attachment.size != null) {
|
||||
"${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 {
|
||||
const val ACTION_VIEW = "view"
|
||||
const val ACTION_HTTP = "http"
|
||||
|
|
|
@ -10,7 +10,6 @@ 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
|
||||
|
@ -89,16 +88,23 @@ 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()
|
||||
val preferenceKey = getString(R.string.eos_preference_key_is_enabled)
|
||||
if (PreferenceManager.getDefaultSharedPreferences(this).getBoolean(preferenceKey, false)) {
|
||||
sendBroadcast(Intent(this, AutoRestartReceiver::class.java))
|
||||
}
|
||||
|
||||
sendBroadcast(Intent(this, AutoRestartReceiver::class.java)) // Restart it if necessary!
|
||||
super.onDestroy()
|
||||
}
|
||||
|
||||
|
@ -132,6 +138,7 @@ class SubscriberService : Service() {
|
|||
}
|
||||
}
|
||||
wakeLock = null
|
||||
stopForeground(true)
|
||||
stopSelf()
|
||||
} catch (e: Exception) {
|
||||
Log.d(TAG, "Service stopped without being started: ${e.message}")
|
||||
|
|
|
@ -2,11 +2,10 @@ package io.heckel.ntfy.service
|
|||
|
||||
import android.content.Context
|
||||
import android.content.Intent
|
||||
import androidx.preference.PreferenceManager
|
||||
import androidx.core.content.ContextCompat
|
||||
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
|
||||
|
||||
|
@ -44,17 +43,11 @@ 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 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 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 serviceState = SubscriberService.readServiceState(context)
|
||||
if (serviceState == SubscriberService.ServiceState.STOPPED && action == SubscriberService.Action.STOP) {
|
||||
return@withContext Result.success()
|
||||
|
@ -62,7 +55,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
|
||||
context.startService(it)
|
||||
ContextCompat.startForegroundService(context, it)
|
||||
}
|
||||
}
|
||||
return Result.success()
|
||||
|
|
|
@ -9,6 +9,8 @@ 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
|
||||
|
@ -33,20 +35,23 @@ 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, view, selected, onClick, onLongClick)
|
||||
return DetailViewHolder(activity, lifecycleScope, repository, markwon, view, selected, onClick, onLongClick)
|
||||
}
|
||||
|
||||
/* 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. */
|
||||
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) {
|
||||
private var notification: Notification? = null
|
||||
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 unmatchedTags = unmatchedTags(splitTags(notification.tags))
|
||||
val message = maybeAppendActionErrors(formatMessage(notification), notification)
|
||||
|
||||
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 {
|
||||
// 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.
|
||||
|
@ -143,6 +165,13 @@ 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 -> {
|
||||
|
|
|
@ -1,71 +0,0 @@
|
|||
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()
|
||||
}
|
||||
}
|
|
@ -1,37 +0,0 @@
|
|||
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
|
||||
}
|
||||
}
|
||||
}
|
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
|
||||
.orEmpty()
|
||||
.mapNotNull { action -> action.error }
|
||||
|
|
|
@ -1,31 +1,31 @@
|
|||
<vector xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
android:width="22dp"
|
||||
android:height="22dp"
|
||||
android:width="50dp"
|
||||
android:height="50dp"
|
||||
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="?android:attr/colorControlNormal"
|
||||
android:fillColor="#FFFFFFFF"
|
||||
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="?android:attr/colorControlNormal"
|
||||
android:fillColor="#FFFFFFFF"
|
||||
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="?android:attr/colorControlNormal"
|
||||
android:fillColor="#FFFFFFFF"
|
||||
android:strokeColor="#00000000"/>
|
||||
<path
|
||||
android:pathData="m26.7503,30.9206l11.6118,0l0,3.1388L26.7503,34.0594Z"
|
||||
android:strokeWidth="0.525121"
|
||||
android:fillColor="?android:attr/colorControlNormal"
|
||||
android:fillColor="#FFFFFFFF"
|
||||
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="?android:attr/colorControlNormal"
|
||||
android:fillColor="#FFFFFFFF"
|
||||
android:strokeColor="#00000000"/>
|
||||
</vector>
|
||||
|
|
|
@ -1,49 +0,0 @@
|
|||
<?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>
|
|
@ -343,6 +343,4 @@
|
|||
<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>
|
|
@ -343,6 +343,4 @@
|
|||
<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>
|
|
@ -343,6 +343,4 @@
|
|||
<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>
|
|
@ -330,6 +330,4 @@
|
|||
<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>
|
|
@ -391,10 +391,4 @@
|
|||
<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>
|
||||
|
|
|
@ -30,26 +30,4 @@
|
|||
<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>
|
||||
|
|
|
@ -1,11 +0,0 @@
|
|||
<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>
|
|
@ -92,6 +92,7 @@ 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"]
|
||||
|
@ -132,6 +133,7 @@ class FirebaseService : FirebaseMessagingService() {
|
|||
timestamp = timestamp,
|
||||
title = title ?: "",
|
||||
message = message,
|
||||
contentType = contentType ?: "",
|
||||
encoding = encoding ?: "",
|
||||
priority = toPriority(priority),
|
||||
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 |
|
@ -18,7 +18,6 @@ allprojects {
|
|||
repositories {
|
||||
google()
|
||||
mavenCentral()
|
||||
maven { url 'https://gitlab.e.foundation/api/v4/groups/9/-/packages/maven'}
|
||||
maven { url "https://jitpack.io" } // For StfalconImageViewer
|
||||
}
|
||||
}
|
||||
|
|
Loading…
Reference in a new issue