Remove all links to ntfy.sh and GitHub, and even copy URL menu items

This commit is contained in:
Philipp Heckel 2025-10-12 17:57:55 -04:00
parent ab72855403
commit 211e840461
8 changed files with 71 additions and 17 deletions

View file

@ -18,8 +18,8 @@ android {
minSdkVersion 21
targetSdkVersion 35
versionCode 44
versionName "1.17.11"
versionCode 46
versionName "1.17.13"
testInstrumentationRunner "androidx.test.runner.AndroidJUnitRunner"
@ -53,11 +53,13 @@ android {
play {
buildConfigField 'boolean', 'FIREBASE_AVAILABLE', 'true'
buildConfigField 'boolean', 'RATE_APP_AVAILABLE', 'true'
buildConfigField 'boolean', 'PAYMENT_LINKS_AVAILABLE', 'false' // Google Play Payments Policy, see #1463
buildConfigField 'boolean', 'INSTALL_PACKAGES_AVAILABLE', 'false'
}
fdroid {
buildConfigField 'boolean', 'FIREBASE_AVAILABLE', 'false'
buildConfigField 'boolean', 'RATE_APP_AVAILABLE', 'false'
buildConfigField 'boolean', 'PAYMENT_LINKS_AVAILABLE', 'true'
buildConfigField 'boolean', 'INSTALL_PACKAGES_AVAILABLE', 'true'
}
}

View file

@ -29,6 +29,7 @@ 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)
private val appBaseUrl = context.getString(R.string.app_base_url)
fun display(subscription: Subscription, notification: Notification) {
Log.d(TAG, "Displaying notification $notification")
@ -91,7 +92,7 @@ class NotificationService(val context: Context) {
}
private fun displayInternal(subscription: Subscription, notification: Notification, update: Boolean = false) {
val title = formatTitle(subscription, notification)
val title = formatTitle(appBaseUrl, subscription, notification)
val groupId = if (subscription.dedicatedChannels) subscriptionGroupId(subscription) else DEFAULT_GROUP
val channelId = toChannelId(groupId, notification.priority)
val insistent = notification.priority == PRIORITY_MAX &&
@ -365,7 +366,7 @@ class NotificationService(val context: Context) {
putExtra(MainActivity.EXTRA_SUBSCRIPTION_ID, subscription.id)
putExtra(MainActivity.EXTRA_SUBSCRIPTION_BASE_URL, subscription.baseUrl)
putExtra(MainActivity.EXTRA_SUBSCRIPTION_TOPIC, subscription.topic)
putExtra(MainActivity.EXTRA_SUBSCRIPTION_DISPLAY_NAME, displayName(subscription))
putExtra(MainActivity.EXTRA_SUBSCRIPTION_DISPLAY_NAME, displayName(appBaseUrl, subscription))
putExtra(MainActivity.EXTRA_SUBSCRIPTION_INSTANT, subscription.instant)
putExtra(MainActivity.EXTRA_SUBSCRIPTION_MUTED_UNTIL, subscription.mutedUntil)
}

View file

@ -19,6 +19,7 @@ import android.widget.Toast
import androidx.activity.viewModels
import androidx.appcompat.app.AppCompatActivity
import androidx.core.content.ContextCompat
import androidx.core.view.isVisible
import androidx.lifecycle.lifecycleScope
import androidx.recyclerview.widget.ItemTouchHelper
import androidx.recyclerview.widget.RecyclerView
@ -81,6 +82,10 @@ class DetailActivity : AppCompatActivity(), ActionMode.Callback, NotificationFra
// Show 'Back' button
supportActionBar?.setDisplayHomeAsUpEnabled(true)
// Hide links that lead to payments, see https://github.com/binwiederhier/ntfy/issues/1463
val howToLink = findViewById<TextView>(R.id.detail_how_to_link)
howToLink.isVisible = BuildConfig.PAYMENT_LINKS_AVAILABLE
// Handle direct deep links to topic "ntfy://..."
val url = intent?.data
if (intent?.action == ACTION_VIEW && url != null) {
@ -152,7 +157,7 @@ class DetailActivity : AppCompatActivity(), ActionMode.Callback, NotificationFra
intent.putExtra(MainActivity.EXTRA_SUBSCRIPTION_ID, subscription.id)
intent.putExtra(MainActivity.EXTRA_SUBSCRIPTION_BASE_URL, subscription.baseUrl)
intent.putExtra(MainActivity.EXTRA_SUBSCRIPTION_TOPIC, subscription.topic)
intent.putExtra(MainActivity.EXTRA_SUBSCRIPTION_DISPLAY_NAME, displayName(subscription))
intent.putExtra(MainActivity.EXTRA_SUBSCRIPTION_DISPLAY_NAME, displayName(appBaseUrl, subscription))
intent.putExtra(MainActivity.EXTRA_SUBSCRIPTION_INSTANT, subscription.instant)
intent.putExtra(MainActivity.EXTRA_SUBSCRIPTION_MUTED_UNTIL, subscription.mutedUntil)
@ -277,10 +282,11 @@ class DetailActivity : AppCompatActivity(), ActionMode.Callback, NotificationFra
val subscription = repository.getSubscription(subscriptionId) ?: return@launch
subscriptionInstant = subscription.instant
subscriptionMutedUntil = subscription.mutedUntil
subscriptionDisplayName = displayName(subscription)
subscriptionDisplayName = displayName(appBaseUrl, subscription)
showHideInstantMenuItems(subscriptionInstant)
showHideMutedUntilMenuItems(subscriptionMutedUntil)
showHideCopyMenuItems(subscription.baseUrl)
updateTitle(subscriptionDisplayName)
}
}
@ -316,6 +322,7 @@ class DetailActivity : AppCompatActivity(), ActionMode.Callback, NotificationFra
// Show and hide buttons
showHideInstantMenuItems(subscriptionInstant)
showHideMutedUntilMenuItems(subscriptionMutedUntil)
showHideCopyMenuItems(subscriptionBaseUrl)
// Regularly check if "notification muted" time has passed
// NOTE: This is done here, because then we know that we've initialized the menu items.
@ -559,6 +566,18 @@ class DetailActivity : AppCompatActivity(), ActionMode.Callback, NotificationFra
}
}
private fun showHideCopyMenuItems(subscriptionBaseUrl: String) {
if (!this::menu.isInitialized) {
return
}
runOnUiThread {
// Hide links that lead to payments, see https://github.com/binwiederhier/ntfy/issues/1463
val copyUrlItem = menu.findItem(R.id.detail_menu_copy_url)
copyUrlItem?.isVisible = appBaseUrl != subscriptionBaseUrl || BuildConfig.PAYMENT_LINKS_AVAILABLE
}
}
private fun updateTitle(subscriptionDisplayName: String) {
runOnUiThread {
title = subscriptionDisplayName

View file

@ -87,6 +87,7 @@ class DetailSettingsActivity : AppCompatActivity() {
private lateinit var openChannelsPref: Preference
private lateinit var iconSetLauncher: ActivityResultLauncher<String>
private lateinit var iconRemovePref: Preference
private lateinit var appBaseUrl: String
override fun onCreatePreferences(savedInstanceState: Bundle?, rootKey: String?) {
setPreferencesFromResource(R.xml.detail_preferences, rootKey)
@ -96,6 +97,7 @@ class DetailSettingsActivity : AppCompatActivity() {
serviceManager = SubscriberServiceManager(requireActivity())
notificationService = NotificationService(requireActivity())
resolver = requireContext().applicationContext.contentResolver
appBaseUrl = requireContext().getString(R.string.app_base_url)
// Create result launcher for custom icon (must be created in onCreatePreferences() directly)
iconSetLauncher = createIconPickLauncher()
@ -381,7 +383,7 @@ class DetailSettingsActivity : AppCompatActivity() {
save(newSubscription)
// Update activity title
activity?.runOnUiThread {
activity?.title = displayName(newSubscription)
activity?.title = displayName(appBaseUrl, newSubscription)
}
// Update dedicated notification channel
if (newSubscription.dedicatedChannels) {
@ -394,9 +396,10 @@ class DetailSettingsActivity : AppCompatActivity() {
}
pref?.summaryProvider = Preference.SummaryProvider<EditTextPreference> { provider ->
if (TextUtils.isEmpty(provider.text)) {
val appBaseUrl = context?.getString(R.string.app_base_url)
getString(
R.string.detail_settings_appearance_display_name_default_summary,
displayName(subscription)
displayName(appBaseUrl, subscription)
)
} else {
provider.text

View file

@ -14,6 +14,7 @@ import android.os.Bundle
import android.provider.Settings
import android.provider.Settings.ACTION_REQUEST_SCHEDULE_EXACT_ALARM
import android.text.method.LinkMovementMethod
import android.text.util.Linkify
import android.view.ActionMode
import android.view.Menu
import android.view.MenuItem
@ -26,6 +27,8 @@ import androidx.appcompat.app.AppCompatActivity
import androidx.appcompat.app.AppCompatDelegate
import androidx.core.app.ActivityCompat
import androidx.core.content.ContextCompat
import androidx.core.text.HtmlCompat
import androidx.core.view.isVisible
import androidx.lifecycle.lifecycleScope
import androidx.recyclerview.widget.RecyclerView
import androidx.swiperefreshlayout.widget.SwipeRefreshLayout
@ -227,6 +230,10 @@ class MainActivity : AppCompatActivity(), ActionMode.Callback, AddFragment.Subsc
}
}
// Hide links that lead to payments, see https://github.com/binwiederhier/ntfy/issues/1463
val howToLink = findViewById<TextView>(R.id.main_how_to_link)
howToLink.isVisible = BuildConfig.PAYMENT_LINKS_AVAILABLE
// Create notification channels right away, so we can configure them immediately after installing the app
dispatcher?.init()
@ -276,7 +283,19 @@ class MainActivity : AppCompatActivity(), ActionMode.Callback, AddFragment.Subsc
val wsRemindTimeReached = repository.getWebSocketRemindTime() < System.currentTimeMillis()
val showBanner = hasSelfHostedSubscriptions && wsRemindTimeReached && !usingWebSockets
val wsBanner = findViewById<View>(R.id.main_banner_websocket)
wsBanner.visibility = if (showBanner) View.VISIBLE else View.GONE
if (showBanner) {
wsBanner.visibility = View.VISIBLE
if (!BuildConfig.PAYMENT_LINKS_AVAILABLE) {
// Hide links that lead to payments, see https://github.com/binwiederhier/ntfy/issues/1463
// This is a big fat hack, but I have to release this quickly ...
val wsBannerMainText = findViewById<TextView>(R.id.main_banner_websocket_text)
val raw = getString(R.string.main_banner_websocket_text)
val unlinked = raw.replace(Regex("</?a[^>]*>"), "")
wsBannerMainText.text = HtmlCompat.fromHtml(unlinked, HtmlCompat.FROM_HTML_MODE_LEGACY)
}
} else {
wsBanner.visibility = View.GONE
}
}
private fun showHideWebSocketReconnectBanner(subscriptions: List<Subscription>) {
@ -397,7 +416,11 @@ class MainActivity : AppCompatActivity(), ActionMode.Callback, AddFragment.Subsc
runOnUiThread {
// Show/hide menu items based on build config
val rateAppItem = menu.findItem(R.id.main_menu_rate)
val docsItem = menu.findItem(R.id.main_menu_docs)
val reportBugItem = menu.findItem(R.id.main_menu_report_bug)
rateAppItem.isVisible = BuildConfig.RATE_APP_AVAILABLE
docsItem.isVisible = BuildConfig.PAYMENT_LINKS_AVAILABLE // Google Payments Policy, see https://github.com/binwiederhier/ntfy/issues/1463
reportBugItem.isVisible = BuildConfig.PAYMENT_LINKS_AVAILABLE // Google Payments Policy, see https://github.com/binwiederhier/ntfy/issues/1463
// Pause notification icons
val notificationsEnabledItem = menu.findItem(R.id.main_menu_notifications_enabled)
@ -570,7 +593,7 @@ class MainActivity : AppCompatActivity(), ActionMode.Callback, AddFragment.Subsc
}
}
} catch (e: Exception) {
val topic = displayName(subscription)
val topic = displayName(appBaseUrl, subscription)
if (errorMessage == "") errorMessage = "$topic: ${e.message}"
errors++
}
@ -597,7 +620,7 @@ class MainActivity : AppCompatActivity(), ActionMode.Callback, AddFragment.Subsc
intent.putExtra(EXTRA_SUBSCRIPTION_ID, subscription.id)
intent.putExtra(EXTRA_SUBSCRIPTION_BASE_URL, subscription.baseUrl)
intent.putExtra(EXTRA_SUBSCRIPTION_TOPIC, subscription.topic)
intent.putExtra(EXTRA_SUBSCRIPTION_DISPLAY_NAME, displayName(subscription))
intent.putExtra(EXTRA_SUBSCRIPTION_DISPLAY_NAME, displayName(appBaseUrl, subscription))
intent.putExtra(EXTRA_SUBSCRIPTION_INSTANT, subscription.instant)
intent.putExtra(EXTRA_SUBSCRIPTION_MUTED_UNTIL, subscription.mutedUntil)
startActivity(intent)
@ -610,7 +633,7 @@ class MainActivity : AppCompatActivity(), ActionMode.Callback, AddFragment.Subsc
intent.putExtra(DetailActivity.EXTRA_SUBSCRIPTION_ID, subscription.id)
intent.putExtra(DetailActivity.EXTRA_SUBSCRIPTION_BASE_URL, subscription.baseUrl)
intent.putExtra(DetailActivity.EXTRA_SUBSCRIPTION_TOPIC, subscription.topic)
intent.putExtra(DetailActivity.EXTRA_SUBSCRIPTION_DISPLAY_NAME, displayName(subscription))
intent.putExtra(DetailActivity.EXTRA_SUBSCRIPTION_DISPLAY_NAME, displayName(appBaseUrl, subscription))
startActivity(intent)
}

View file

@ -64,6 +64,7 @@ class MainAdapter(private val repository: Repository, private val onClick: (Subs
private val notificationDisabledForeverImageView: View = itemView.findViewById(R.id.main_item_notification_disabled_forever_image)
private val instantImageView: View = itemView.findViewById(R.id.main_item_instant_image)
private val newItemsView: TextView = itemView.findViewById(R.id.main_item_new)
private val appBaseUrl = context.getString(R.string.app_base_url)
fun bind(subscription: Subscription) {
this.subscription = subscription
@ -99,7 +100,7 @@ class MainAdapter(private val repository: Repository, private val onClick: (Subs
} else {
imageView.setImageResource(R.drawable.ic_sms_gray_24dp)
}
nameView.text = displayName(subscription)
nameView.text = displayName(appBaseUrl, subscription)
statusView.text = statusMessage
dateView.text = dateText
dateView.visibility = if (isUnifiedPush) View.GONE else View.VISIBLE

View file

@ -64,8 +64,13 @@ fun subscriptionTopicShortUrl(subscription: Subscription) : String {
return topicShortUrl(subscription.baseUrl, subscription.topic)
}
fun displayName(subscription: Subscription) : String {
return subscription.displayName ?: subscriptionTopicShortUrl(subscription)
fun displayName(appBaseUrl: String?, subscription: Subscription) : String {
if (subscription.displayName != null) {
return subscription.displayName
} else if (appBaseUrl == subscription.baseUrl) {
return subscription.topic
}
return subscriptionTopicShortUrl(subscription)
}
fun shortUrl(url: String) = url
@ -186,11 +191,11 @@ fun decodeBytesMessage(notification: Notification): ByteArray {
* See above; prepend emojis to title if the title is non-empty.
* Otherwise, they are prepended to the message.
*/
fun formatTitle(subscription: Subscription, notification: Notification): String {
fun formatTitle(appBaseUrl: String?, subscription: Subscription, notification: Notification): String {
return if (notification.title != "") {
formatTitle(notification)
} else {
displayName(subscription)
displayName(appBaseUrl, subscription)
}
}