diff --git a/app/build.gradle b/app/build.gradle index dafaeb80..e86bb4df 100644 --- a/app/build.gradle +++ b/app/build.gradle @@ -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' } } diff --git a/app/src/main/java/io/heckel/ntfy/msg/NotificationService.kt b/app/src/main/java/io/heckel/ntfy/msg/NotificationService.kt index 48067073..24fc0032 100644 --- a/app/src/main/java/io/heckel/ntfy/msg/NotificationService.kt +++ b/app/src/main/java/io/heckel/ntfy/msg/NotificationService.kt @@ -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) } diff --git a/app/src/main/java/io/heckel/ntfy/ui/DetailActivity.kt b/app/src/main/java/io/heckel/ntfy/ui/DetailActivity.kt index 851f43ac..0dd15499 100644 --- a/app/src/main/java/io/heckel/ntfy/ui/DetailActivity.kt +++ b/app/src/main/java/io/heckel/ntfy/ui/DetailActivity.kt @@ -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(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 diff --git a/app/src/main/java/io/heckel/ntfy/ui/DetailSettingsActivity.kt b/app/src/main/java/io/heckel/ntfy/ui/DetailSettingsActivity.kt index ccc0191d..5d2af6a7 100644 --- a/app/src/main/java/io/heckel/ntfy/ui/DetailSettingsActivity.kt +++ b/app/src/main/java/io/heckel/ntfy/ui/DetailSettingsActivity.kt @@ -87,6 +87,7 @@ class DetailSettingsActivity : AppCompatActivity() { private lateinit var openChannelsPref: Preference private lateinit var iconSetLauncher: ActivityResultLauncher 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 { 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 diff --git a/app/src/main/java/io/heckel/ntfy/ui/MainActivity.kt b/app/src/main/java/io/heckel/ntfy/ui/MainActivity.kt index 5ce60f42..5655826a 100644 --- a/app/src/main/java/io/heckel/ntfy/ui/MainActivity.kt +++ b/app/src/main/java/io/heckel/ntfy/ui/MainActivity.kt @@ -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(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(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(R.id.main_banner_websocket_text) + val raw = getString(R.string.main_banner_websocket_text) + val unlinked = raw.replace(Regex("]*>"), "") + wsBannerMainText.text = HtmlCompat.fromHtml(unlinked, HtmlCompat.FROM_HTML_MODE_LEGACY) + } + } else { + wsBanner.visibility = View.GONE + } } private fun showHideWebSocketReconnectBanner(subscriptions: List) { @@ -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) } diff --git a/app/src/main/java/io/heckel/ntfy/ui/MainAdapter.kt b/app/src/main/java/io/heckel/ntfy/ui/MainAdapter.kt index ed0d2bd0..905db7c8 100644 --- a/app/src/main/java/io/heckel/ntfy/ui/MainAdapter.kt +++ b/app/src/main/java/io/heckel/ntfy/ui/MainAdapter.kt @@ -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 diff --git a/app/src/main/java/io/heckel/ntfy/util/Util.kt b/app/src/main/java/io/heckel/ntfy/util/Util.kt index a658a7fe..605a87d3 100644 --- a/app/src/main/java/io/heckel/ntfy/util/Util.kt +++ b/app/src/main/java/io/heckel/ntfy/util/Util.kt @@ -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) } } diff --git a/fastlane/metadata/android/en-US/changelog/44.txt b/fastlane/metadata/android/en-US/changelog/46.txt similarity index 100% rename from fastlane/metadata/android/en-US/changelog/44.txt rename to fastlane/metadata/android/en-US/changelog/46.txt