From adeb8ebfbdca23dc7b3db0a2f38947a1afb7e3ed Mon Sep 17 00:00:00 2001 From: Philipp Heckel Date: Sun, 4 Jan 2026 23:54:13 -0500 Subject: [PATCH] Looks great now --- .../ntfy/ui/CertificateSettingsFragment.kt | 2 +- .../ntfy/ui/ClientCertificateFragment.kt | 31 ++++++-------- .../ntfy/ui/TrustedCertificateFragment.kt | 9 ++++ .../fragment_client_certificate_dialog.xml | 31 +++++++------- .../fragment_trusted_certificate_dialog.xml | 42 +++++++++++++++++++ app/src/main/res/values/strings.xml | 17 ++++---- 6 files changed, 91 insertions(+), 41 deletions(-) diff --git a/app/src/main/java/io/heckel/ntfy/ui/CertificateSettingsFragment.kt b/app/src/main/java/io/heckel/ntfy/ui/CertificateSettingsFragment.kt index d857186e..55478ede 100644 --- a/app/src/main/java/io/heckel/ntfy/ui/CertificateSettingsFragment.kt +++ b/app/src/main/java/io/heckel/ntfy/ui/CertificateSettingsFragment.kt @@ -169,7 +169,7 @@ class CertificateSettingsFragment : BasePreferenceFragment(), it.readBytes() } if (data != null && data.isNotEmpty()) { - ClientCertificateFragment.newInstance(data) + ClientCertificateFragment.newInstanceAdd(data) .show(childFragmentManager, ClientCertificateFragment.TAG) } else { Toast.makeText(context, R.string.settings_advanced_certificates_error_invalid_p12, Toast.LENGTH_SHORT).show() diff --git a/app/src/main/java/io/heckel/ntfy/ui/ClientCertificateFragment.kt b/app/src/main/java/io/heckel/ntfy/ui/ClientCertificateFragment.kt index de685a35..812d120d 100644 --- a/app/src/main/java/io/heckel/ntfy/ui/ClientCertificateFragment.kt +++ b/app/src/main/java/io/heckel/ntfy/ui/ClientCertificateFragment.kt @@ -14,7 +14,6 @@ import androidx.core.view.isVisible import androidx.fragment.app.DialogFragment import androidx.lifecycle.lifecycleScope import com.google.android.material.appbar.MaterialToolbar -import com.google.android.material.dialog.MaterialAlertDialogBuilder import com.google.android.material.textfield.TextInputEditText import com.google.android.material.textfield.TextInputLayout import io.heckel.ntfy.R @@ -64,6 +63,7 @@ class ClientCertificateFragment : DialogFragment() { // Page 2 views private lateinit var page2Layout: LinearLayout + private lateinit var descriptionPage2Text: TextView private lateinit var baseUrlValueText: TextView private lateinit var subjectText: TextView private lateinit var issuerText: TextView @@ -143,7 +143,7 @@ class ClientCertificateFragment : DialogFragment() { true } R.id.client_certificate_action_delete -> { - confirmDeleteCertificate() + deleteCertificate() true } else -> false @@ -163,6 +163,7 @@ class ClientCertificateFragment : DialogFragment() { // Page 2 views page2Layout = view.findViewById(R.id.client_certificate_page2) + descriptionPage2Text = view.findViewById(R.id.client_certificate_description_page2) baseUrlValueText = view.findViewById(R.id.client_certificate_base_url_value) subjectText = view.findViewById(R.id.client_certificate_subject) issuerText = view.findViewById(R.id.client_certificate_issuer) @@ -197,7 +198,7 @@ class ClientCertificateFragment : DialogFragment() { deleteMenuItem.isVisible = true // Hide description for view mode - view?.findViewById(R.id.client_certificate_description_page2)?.isVisible = false + descriptionPage2Text.isVisible = false // Load certificate from repository lifecycleScope.launch(Dispatchers.IO) { @@ -301,22 +302,16 @@ class ClientCertificateFragment : DialogFragment() { } } - private fun confirmDeleteCertificate() { + private fun deleteCertificate() { val url = baseUrl ?: return - MaterialAlertDialogBuilder(requireContext()) - .setMessage(R.string.client_certificate_dialog_delete_confirm) - .setPositiveButton(R.string.client_certificate_dialog_button_delete) { _, _ -> - lifecycleScope.launch(Dispatchers.IO) { - repository.removeClientCertificate(url) - withContext(Dispatchers.Main) { - Toast.makeText(context, R.string.client_certificate_dialog_deleted_toast, Toast.LENGTH_SHORT).show() - listener?.onCertificateDeleted() - dismiss() - } - } + lifecycleScope.launch(Dispatchers.IO) { + repository.removeClientCertificate(url) + withContext(Dispatchers.Main) { + Toast.makeText(context, R.string.client_certificate_dialog_deleted_toast, Toast.LENGTH_SHORT).show() + listener?.onCertificateDeleted() + dismiss() } - .setNegativeButton(R.string.client_certificate_dialog_button_cancel, null) - .show() + } } private fun handleBack() { @@ -357,7 +352,7 @@ class ClientCertificateFragment : DialogFragment() { /** * Create fragment for ADD mode - two-page flow to add a client certificate */ - fun newInstance(pkcs12Data: ByteArray): ClientCertificateFragment { + fun newInstanceAdd(pkcs12Data: ByteArray): ClientCertificateFragment { return ClientCertificateFragment().apply { arguments = Bundle().apply { putString(ARG_MODE, Mode.ADD.name) diff --git a/app/src/main/java/io/heckel/ntfy/ui/TrustedCertificateFragment.kt b/app/src/main/java/io/heckel/ntfy/ui/TrustedCertificateFragment.kt index 3ae409b9..48cb8708 100644 --- a/app/src/main/java/io/heckel/ntfy/ui/TrustedCertificateFragment.kt +++ b/app/src/main/java/io/heckel/ntfy/ui/TrustedCertificateFragment.kt @@ -49,6 +49,8 @@ class TrustedCertificateFragment : DialogFragment() { private lateinit var fingerprintText: TextView private lateinit var validFromText: TextView private lateinit var validUntilText: TextView + private lateinit var caText: TextView + private lateinit var caInfoText: TextView interface TrustedCertificateListener { fun onCertificateTrusted(certificate: X509Certificate) @@ -145,6 +147,8 @@ class TrustedCertificateFragment : DialogFragment() { fingerprintText = view.findViewById(R.id.trusted_certificate_fingerprint) validFromText = view.findViewById(R.id.trusted_certificate_valid_from) validUntilText = view.findViewById(R.id.trusted_certificate_valid_until) + caText = view.findViewById(R.id.trusted_certificate_ca) + caInfoText = view.findViewById(R.id.trusted_certificate_ca_info) when (mode) { Mode.UNKNOWN -> setupUnknownMode() @@ -211,6 +215,11 @@ class TrustedCertificateFragment : DialogFragment() { validFromText.text = dateFormat.format(certificate.notBefore) validUntilText.text = dateFormat.format(certificate.notAfter) + // Determine if this is a CA certificate (self-signed) + val isCa = certificate.subjectX500Principal == certificate.issuerX500Principal + caText.text = if (isCa) getString(R.string.common_yes) else getString(R.string.common_no) + caInfoText.isVisible = isCa + // Show warning if certificate is expired or not yet valid val now = Date() when { diff --git a/app/src/main/res/layout/fragment_client_certificate_dialog.xml b/app/src/main/res/layout/fragment_client_certificate_dialog.xml index 30d4f1be..9f7bfe5d 100644 --- a/app/src/main/res/layout/fragment_client_certificate_dialog.xml +++ b/app/src/main/res/layout/fragment_client_certificate_dialog.xml @@ -63,10 +63,26 @@ android:paddingTop="16dp" android:paddingBottom="16dp" /> + + + + + + @@ -78,21 +94,6 @@ - - - - - - + + + + + + + + diff --git a/app/src/main/res/values/strings.xml b/app/src/main/res/values/strings.xml index 0f45a154..addac483 100644 --- a/app/src/main/res/values/strings.xml +++ b/app/src/main/res/values/strings.xml @@ -2,6 +2,8 @@ tools:ignore="MissingTranslation"> + Yes + No Copied to clipboard @@ -424,8 +426,8 @@ ntfy can schedule exact alarms. Exact alarms are required to reconnect WebSockets in the background. Click to revoke the permission. ntfy cannot schedule exact alarms. Exact alarms are required to reconnect WebSockets in the background. Click to grant the permission. Manage certificates - Trust self-signed server certificates and manage client certificates for mTLS - Trusted server certificates + Add certificates to the trust store and manage client certificates for mTLS + Trusted certificates Leaf certificate, issued by %1$s\nExpires %2$s Leaf certificate, issued by %1$s\nExpired CA certificate, self-signed\nExpires %1$s @@ -434,7 +436,7 @@ Import a certificate into the trust store (PEM). HTTPS and WebSocket connections will trust this certificate. Client certificates (mTLS) Client certificate, issued by %1$s\nExpires %2$s, used by %3$s - EClient certificate, issued by %1$s\nExpired, used by %2$s + Client certificate, issued by %1$s\nExpired, used by %2$s Add a client certificate Import certificate for mutual TLS authentication (PKCS#12). This certificate will be used when connecting to the server. Invalid certificate file @@ -511,6 +513,8 @@ SHA-256 fingerprint Valid from Valid until + Certificate Authority (CA) + All certificates signed by this Certificate Authority will be trusted. Warning: This certificate has expired. Warning: This certificate is not yet valid. Trust @@ -521,10 +525,11 @@ Client certificate Add client certificate - Enter the password for the PKCS#12 file and the server URL this certificate should be used for. + Enter the service URL this certificate should be used for, and the password for the PKCS#12 file. Review the certificate details and save to add this client certificate. Password - Server URL (e.g. https://example.com) + Service URL + e.g. https://ntfy.example.com Subject Issuer SHA-256 Fingerprint @@ -533,8 +538,6 @@ Next Save Delete - Cancel - Delete this certificate? Certificate added Certificate deleted Wrong password or invalid PKCS#12 file