More advanced dialog

This commit is contained in:
Philipp Heckel 2025-12-26 14:11:26 -05:00
parent 689ebf40a3
commit cb87b4b5f1
7 changed files with 837 additions and 97 deletions

View file

@ -41,7 +41,12 @@ class ApiService {
tags: List<String> = emptyList(),
delay: String = "",
body: RequestBody? = null,
filename: String = ""
filename: String = "",
click: String = "",
attach: String = "",
email: String = "",
call: String = "",
markdown: Boolean = false
) {
val url = topicUrl(baseUrl, topic)
val query = mutableListOf<String>()
@ -60,6 +65,21 @@ class ApiService {
if (filename.isNotEmpty()) {
query.add("filename=${URLEncoder.encode(filename, "UTF-8")}")
}
if (click.isNotEmpty()) {
query.add("click=${URLEncoder.encode(click, "UTF-8")}")
}
if (attach.isNotEmpty()) {
query.add("attach=${URLEncoder.encode(attach, "UTF-8")}")
}
if (email.isNotEmpty()) {
query.add("email=${URLEncoder.encode(email, "UTF-8")}")
}
if (call.isNotEmpty()) {
query.add("call=${URLEncoder.encode(call, "UTF-8")}")
}
if (markdown) {
query.add("markdown=true")
}
if (body != null) {
query.add("message=${URLEncoder.encode(message.replace("\n", "\\n"), "UTF-8")}")
}

View file

@ -0,0 +1,57 @@
package io.heckel.ntfy.ui
import android.content.Context
import android.view.LayoutInflater
import android.view.View
import android.view.ViewGroup
import android.widget.ArrayAdapter
import android.widget.ImageView
import android.widget.TextView
import io.heckel.ntfy.R
data class PriorityItem(
val priority: Int,
val label: String,
val iconResId: Int
)
class PriorityAdapter(
context: Context,
private val items: List<PriorityItem>
) : ArrayAdapter<PriorityItem>(context, R.layout.item_priority_dropdown, items) {
override fun getView(position: Int, convertView: View?, parent: ViewGroup): View {
return createItemView(position, convertView, parent)
}
override fun getDropDownView(position: Int, convertView: View?, parent: ViewGroup): View {
return createItemView(position, convertView, parent)
}
private fun createItemView(position: Int, convertView: View?, parent: ViewGroup): View {
val view = convertView ?: LayoutInflater.from(context)
.inflate(R.layout.item_priority_dropdown, parent, false)
val item = items[position]
val iconView = view.findViewById<ImageView>(R.id.priority_icon)
val textView = view.findViewById<TextView>(R.id.priority_text)
iconView.setImageResource(item.iconResId)
textView.text = item.label
return view
}
companion object {
fun createPriorityItems(context: Context): List<PriorityItem> {
return listOf(
PriorityItem(1, context.getString(R.string.publish_dialog_priority_min), R.drawable.ic_priority_1_24dp),
PriorityItem(2, context.getString(R.string.publish_dialog_priority_low), R.drawable.ic_priority_2_24dp),
PriorityItem(3, context.getString(R.string.publish_dialog_priority_default), R.drawable.ic_priority_3_24dp),
PriorityItem(4, context.getString(R.string.publish_dialog_priority_high), R.drawable.ic_priority_4_24dp),
PriorityItem(5, context.getString(R.string.publish_dialog_priority_max), R.drawable.ic_priority_5_24dp)
)
}
}
}

View file

@ -2,19 +2,29 @@ package io.heckel.ntfy.ui
import android.app.Dialog
import android.content.Context
import android.content.Intent
import android.net.Uri
import android.os.Bundle
import android.provider.OpenableColumns
import android.view.MenuItem
import android.view.View
import android.view.ViewGroup
import android.view.inputmethod.InputMethodManager
import android.widget.ArrayAdapter
import android.widget.AutoCompleteTextView
import android.widget.CheckBox
import android.widget.ImageButton
import android.widget.ImageView
import android.widget.ProgressBar
import android.widget.TextView
import android.widget.Toast
import androidx.activity.result.ActivityResultLauncher
import androidx.activity.result.contract.ActivityResultContracts
import androidx.fragment.app.DialogFragment
import androidx.lifecycle.lifecycleScope
import com.google.android.material.appbar.MaterialToolbar
import com.google.android.material.button.MaterialButton
import com.google.android.material.chip.Chip
import com.google.android.material.chip.ChipGroup
import com.google.android.material.textfield.TextInputEditText
import io.heckel.ntfy.R
import io.heckel.ntfy.db.Repository
@ -24,27 +34,71 @@ import io.heckel.ntfy.util.AfterChangedTextWatcher
import io.heckel.ntfy.util.topicShortUrl
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.launch
import okhttp3.MediaType.Companion.toMediaType
import okhttp3.RequestBody.Companion.toRequestBody
class PublishFragment : DialogFragment() {
private val api = ApiService()
private lateinit var repository: Repository
// Toolbar
private lateinit var toolbar: MaterialToolbar
private lateinit var sendMenuItem: MenuItem
// Main fields
private lateinit var titleText: TextInputEditText
private lateinit var messageText: TextInputEditText
private lateinit var markdownCheckbox: CheckBox
private lateinit var tagsText: TextInputEditText
private lateinit var priorityDropdown: AutoCompleteTextView
// Chips
private lateinit var chipGroup: ChipGroup
private lateinit var chipClickUrl: Chip
private lateinit var chipEmail: Chip
private lateinit var chipDelay: Chip
private lateinit var chipAttachUrl: Chip
private lateinit var chipAttachFile: Chip
private lateinit var chipPhoneCall: Chip
// Optional field layouts
private lateinit var clickUrlLayout: View
private lateinit var emailLayout: View
private lateinit var delayLayout: View
private lateinit var attachUrlLayout: View
private lateinit var attachFileLayout: View
private lateinit var phoneCallLayout: View
// Optional field inputs
private lateinit var clickUrlText: TextInputEditText
private lateinit var emailText: TextInputEditText
private lateinit var delayText: TextInputEditText
private lateinit var attachUrlText: TextInputEditText
private lateinit var attachFilenameText: TextInputEditText
private lateinit var phoneCallText: TextInputEditText
// Attach file
private lateinit var attachFileButton: MaterialButton
private lateinit var attachFileName: TextView
// Progress/Error
private lateinit var progress: ProgressBar
private lateinit var errorText: TextView
private lateinit var errorImage: View
private lateinit var docsLink: TextView
// State
private var baseUrl: String = ""
private var topic: String = ""
private var selectedPriority: Int = 3 // Default priority
private var initialMessage: String = ""
private var selectedFileUri: Uri? = null
private var selectedFileName: String = ""
private var selectedFileMimeType: String = "application/octet-stream"
// File picker
private lateinit var filePickerLauncher: ActivityResultLauncher<Intent>
interface PublishListener {
fun onPublished()
@ -59,6 +113,17 @@ class PublishFragment : DialogFragment() {
}
}
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
filePickerLauncher = registerForActivityResult(ActivityResultContracts.StartActivityForResult()) { result ->
if (result.resultCode == android.app.Activity.RESULT_OK) {
result.data?.data?.let { uri ->
handleSelectedFile(uri)
}
}
}
}
override fun onCreateDialog(savedInstanceState: Bundle?): Dialog {
if (activity == null) {
throw IllegalStateException("Activity cannot be null")
@ -91,33 +156,75 @@ class PublishFragment : DialogFragment() {
}
sendMenuItem = toolbar.menu.findItem(R.id.publish_dialog_send_button)
// Fields
// Main fields
titleText = view.findViewById(R.id.publish_dialog_title_text)
messageText = view.findViewById(R.id.publish_dialog_message_text)
markdownCheckbox = view.findViewById(R.id.publish_dialog_markdown_checkbox)
tagsText = view.findViewById(R.id.publish_dialog_tags_text)
priorityDropdown = view.findViewById(R.id.publish_dialog_priority_dropdown)
progress = view.findViewById(R.id.publish_dialog_progress)
errorText = view.findViewById(R.id.publish_dialog_error_text)
errorImage = view.findViewById(R.id.publish_dialog_error_image)
docsLink = view.findViewById(R.id.publish_dialog_docs_link)
// Set initial message if provided
if (initialMessage.isNotEmpty()) {
messageText.setText(initialMessage)
}
// Setup priority dropdown
val priorities = listOf(
getString(R.string.publish_dialog_priority_min),
getString(R.string.publish_dialog_priority_low),
getString(R.string.publish_dialog_priority_default),
getString(R.string.publish_dialog_priority_high),
getString(R.string.publish_dialog_priority_max)
)
val adapter = ArrayAdapter(requireContext(), android.R.layout.simple_dropdown_item_1line, priorities)
priorityDropdown.setAdapter(adapter)
priorityDropdown.setText(priorities[2], false) // Default priority
// Setup priority dropdown with custom adapter
val priorityItems = PriorityAdapter.createPriorityItems(requireContext())
val priorityAdapter = PriorityAdapter(requireContext(), priorityItems)
priorityDropdown.setAdapter(priorityAdapter)
priorityDropdown.setText(priorityItems[2].label, false) // Default priority (index 2 = priority 3)
priorityDropdown.setOnItemClickListener { _, _, position, _ ->
selectedPriority = position + 1 // Priority is 1-5
selectedPriority = priorityItems[position].priority
}
// Setup chips
chipGroup = view.findViewById(R.id.publish_dialog_chip_group)
chipClickUrl = view.findViewById(R.id.publish_dialog_chip_click_url)
chipEmail = view.findViewById(R.id.publish_dialog_chip_email)
chipDelay = view.findViewById(R.id.publish_dialog_chip_delay)
chipAttachUrl = view.findViewById(R.id.publish_dialog_chip_attach_url)
chipAttachFile = view.findViewById(R.id.publish_dialog_chip_attach_file)
chipPhoneCall = view.findViewById(R.id.publish_dialog_chip_phone_call)
// Setup optional field layouts
clickUrlLayout = view.findViewById(R.id.publish_dialog_click_url_layout)
emailLayout = view.findViewById(R.id.publish_dialog_email_layout)
delayLayout = view.findViewById(R.id.publish_dialog_delay_layout)
attachUrlLayout = view.findViewById(R.id.publish_dialog_attach_url_layout)
attachFileLayout = view.findViewById(R.id.publish_dialog_attach_file_layout)
phoneCallLayout = view.findViewById(R.id.publish_dialog_phone_call_layout)
// Setup optional field inputs
clickUrlText = view.findViewById(R.id.publish_dialog_click_url_text)
emailText = view.findViewById(R.id.publish_dialog_email_text)
delayText = view.findViewById(R.id.publish_dialog_delay_text)
attachUrlText = view.findViewById(R.id.publish_dialog_attach_url_text)
attachFilenameText = view.findViewById(R.id.publish_dialog_attach_filename_text)
phoneCallText = view.findViewById(R.id.publish_dialog_phone_call_text)
// Attach file UI
attachFileButton = view.findViewById(R.id.publish_dialog_attach_file_button)
attachFileName = view.findViewById(R.id.publish_dialog_attach_file_name)
// Setup chip click listeners
setupChipListeners()
// Setup remove button listeners
setupRemoveButtonListeners(view)
// Setup file picker button
attachFileButton.setOnClickListener {
openFilePicker()
}
// Setup docs link
docsLink.setOnClickListener {
val intent = Intent(Intent.ACTION_VIEW, Uri.parse("https://docs.ntfy.sh/publish/"))
startActivity(intent)
}
// Validation on text change
@ -136,6 +243,96 @@ class PublishFragment : DialogFragment() {
return dialog
}
private fun setupChipListeners() {
chipClickUrl.setOnCheckedChangeListener { _, isChecked ->
clickUrlLayout.visibility = if (isChecked) View.VISIBLE else View.GONE
if (!isChecked) clickUrlText.setText("")
}
chipEmail.setOnCheckedChangeListener { _, isChecked ->
emailLayout.visibility = if (isChecked) View.VISIBLE else View.GONE
if (!isChecked) emailText.setText("")
}
chipDelay.setOnCheckedChangeListener { _, isChecked ->
delayLayout.visibility = if (isChecked) View.VISIBLE else View.GONE
if (!isChecked) delayText.setText("")
}
chipAttachUrl.setOnCheckedChangeListener { _, isChecked ->
attachUrlLayout.visibility = if (isChecked) View.VISIBLE else View.GONE
if (isChecked) {
// Mutually exclusive with attach file
chipAttachFile.isChecked = false
}
if (!isChecked) {
attachUrlText.setText("")
attachFilenameText.setText("")
}
}
chipAttachFile.setOnCheckedChangeListener { _, isChecked ->
attachFileLayout.visibility = if (isChecked) View.VISIBLE else View.GONE
if (isChecked) {
// Mutually exclusive with attach URL
chipAttachUrl.isChecked = false
}
if (!isChecked) {
selectedFileUri = null
selectedFileName = ""
attachFileName.text = ""
}
}
chipPhoneCall.setOnCheckedChangeListener { _, isChecked ->
phoneCallLayout.visibility = if (isChecked) View.VISIBLE else View.GONE
if (!isChecked) phoneCallText.setText("")
}
}
private fun setupRemoveButtonListeners(view: View) {
view.findViewById<ImageButton>(R.id.publish_dialog_click_url_remove).setOnClickListener {
chipClickUrl.isChecked = false
}
view.findViewById<ImageButton>(R.id.publish_dialog_email_remove).setOnClickListener {
chipEmail.isChecked = false
}
view.findViewById<ImageButton>(R.id.publish_dialog_delay_remove).setOnClickListener {
chipDelay.isChecked = false
}
view.findViewById<ImageButton>(R.id.publish_dialog_attach_url_remove).setOnClickListener {
chipAttachUrl.isChecked = false
}
view.findViewById<ImageButton>(R.id.publish_dialog_attach_file_remove).setOnClickListener {
chipAttachFile.isChecked = false
}
view.findViewById<ImageButton>(R.id.publish_dialog_phone_call_remove).setOnClickListener {
chipPhoneCall.isChecked = false
}
}
private fun openFilePicker() {
val intent = Intent(Intent.ACTION_OPEN_DOCUMENT).apply {
addCategory(Intent.CATEGORY_OPENABLE)
type = "*/*"
}
filePickerLauncher.launch(intent)
}
private fun handleSelectedFile(uri: Uri) {
selectedFileUri = uri
// Get file name and mime type
requireContext().contentResolver.query(uri, null, null, null, null)?.use { cursor ->
val nameIndex = cursor.getColumnIndex(OpenableColumns.DISPLAY_NAME)
cursor.moveToFirst()
selectedFileName = if (nameIndex >= 0) cursor.getString(nameIndex) else "file"
}
selectedFileMimeType = requireContext().contentResolver.getType(uri) ?: "application/octet-stream"
attachFileName.text = selectedFileName
}
override fun onStart() {
super.onStart()
dialog?.window?.apply {
@ -164,6 +361,7 @@ class PublishFragment : DialogFragment() {
private fun onSendClick() {
val title = titleText.text.toString()
val message = messageText.text.toString()
val markdown = markdownCheckbox.isChecked
val tagsString = tagsText.text.toString()
val tags = if (tagsString.isNotEmpty()) {
tagsString.split(",").map { it.trim() }.filter { it.isNotEmpty() }
@ -171,6 +369,14 @@ class PublishFragment : DialogFragment() {
emptyList()
}
// Optional fields
val clickUrl = if (chipClickUrl.isChecked) clickUrlText.text.toString() else ""
val email = if (chipEmail.isChecked) emailText.text.toString() else ""
val delay = if (chipDelay.isChecked) delayText.text.toString() else ""
val attachUrl = if (chipAttachUrl.isChecked) attachUrlText.text.toString() else ""
val attachFilename = if (chipAttachUrl.isChecked) attachFilenameText.text.toString() else ""
val phoneCall = if (chipPhoneCall.isChecked) phoneCallText.text.toString() else ""
progress.visibility = View.VISIBLE
errorText.visibility = View.GONE
errorImage.visibility = View.GONE
@ -179,16 +385,52 @@ class PublishFragment : DialogFragment() {
lifecycleScope.launch(Dispatchers.IO) {
try {
val user = repository.getUser(baseUrl)
api.publish(
baseUrl = baseUrl,
topic = topic,
user = user,
message = message,
title = title,
priority = selectedPriority,
tags = tags,
delay = ""
)
// Handle file attachment
if (chipAttachFile.isChecked && selectedFileUri != null) {
// Read file and send as body
val inputStream = requireContext().contentResolver.openInputStream(selectedFileUri!!)
val bytes = inputStream?.readBytes() ?: ByteArray(0)
inputStream?.close()
val body = bytes.toRequestBody(selectedFileMimeType.toMediaType())
api.publish(
baseUrl = baseUrl,
topic = topic,
user = user,
message = message,
title = title,
priority = selectedPriority,
tags = tags,
delay = delay,
body = body,
filename = selectedFileName,
click = clickUrl,
email = email,
call = phoneCall,
markdown = markdown
)
} else {
// No file attachment
api.publish(
baseUrl = baseUrl,
topic = topic,
user = user,
message = message,
title = title,
priority = selectedPriority,
tags = tags,
delay = delay,
click = clickUrl,
attach = attachUrl,
email = email,
call = phoneCall,
markdown = markdown,
filename = attachFilename
)
}
val activity = activity ?: return@launch
activity.runOnUiThread {
Toast.makeText(activity, R.string.publish_dialog_message_published, Toast.LENGTH_SHORT).show()
@ -227,8 +469,27 @@ class PublishFragment : DialogFragment() {
private fun enableView(enable: Boolean) {
titleText.isEnabled = enable
messageText.isEnabled = enable
markdownCheckbox.isEnabled = enable
tagsText.isEnabled = enable
priorityDropdown.isEnabled = enable
// Chips
chipClickUrl.isEnabled = enable
chipEmail.isEnabled = enable
chipDelay.isEnabled = enable
chipAttachUrl.isEnabled = enable
chipAttachFile.isEnabled = enable
chipPhoneCall.isEnabled = enable
// Optional fields
clickUrlText.isEnabled = enable
emailText.isEnabled = enable
delayText.isEnabled = enable
attachUrlText.isEnabled = enable
attachFilenameText.isEnabled = enable
phoneCallText.isEnabled = enable
attachFileButton.isEnabled = enable
sendMenuItem.isEnabled = enable && messageText.text?.isNotEmpty() == true
}
@ -249,4 +510,3 @@ class PublishFragment : DialogFragment() {
}
}
}

View file

@ -0,0 +1,13 @@
<vector xmlns:android="http://schemas.android.com/apk/res/android"
android:width="24dp"
android:height="24dp"
android:viewportWidth="24"
android:viewportHeight="24">
<path
android:pathData="M4,10h16v1.5H4z"
android:fillColor="#2196F3"/>
<path
android:pathData="M4,13h16v1.5H4z"
android:fillColor="#2196F3"/>
</vector>

View file

@ -39,30 +39,34 @@
android:layout_width="match_parent"
android:layout_height="match_parent">
<androidx.constraintlayout.widget.ConstraintLayout
<LinearLayout
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:orientation="vertical">
<ProgressBar
style="?android:attr/progressBarStyle"
android:layout_width="24dp"
android:layout_height="24dp"
android:id="@+id/publish_dialog_progress"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintTop_toTopOf="parent"
android:indeterminate="true"
android:layout_marginTop="16dp"
android:visibility="gone"/>
<!-- Progress and Error -->
<FrameLayout
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_marginTop="8dp">
<ProgressBar
style="?android:attr/progressBarStyle"
android:layout_width="24dp"
android:layout_height="24dp"
android:id="@+id/publish_dialog_progress"
android:layout_gravity="end"
android:indeterminate="true"
android:visibility="gone"/>
</FrameLayout>
<!-- Title -->
<com.google.android.material.textfield.TextInputLayout
android:id="@+id/publish_dialog_title_layout"
android:layout_width="match_parent"
android:layout_height="wrap_content"
app:layout_constraintTop_toTopOf="parent"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintEnd_toEndOf="parent"
android:layout_marginTop="16dp">
android:layout_marginTop="8dp">
<com.google.android.material.textfield.TextInputEditText
android:id="@+id/publish_dialog_title_text"
@ -75,13 +79,11 @@
</com.google.android.material.textfield.TextInputLayout>
<!-- Message -->
<com.google.android.material.textfield.TextInputLayout
android:id="@+id/publish_dialog_message_layout"
android:layout_width="match_parent"
android:layout_height="wrap_content"
app:layout_constraintTop_toBottomOf="@id/publish_dialog_title_layout"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintEnd_toEndOf="parent"
android:layout_marginTop="10dp">
<com.google.android.material.textfield.TextInputEditText
@ -96,73 +98,415 @@
</com.google.android.material.textfield.TextInputLayout>
<com.google.android.material.textfield.TextInputLayout
android:id="@+id/publish_dialog_tags_layout"
<!-- Format as Markdown checkbox -->
<com.google.android.material.checkbox.MaterialCheckBox
android:id="@+id/publish_dialog_markdown_checkbox"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginTop="4dp"
android:text="@string/publish_dialog_format_markdown"/>
<!-- Tags and Priority row -->
<LinearLayout
android:layout_width="match_parent"
android:layout_height="wrap_content"
app:layout_constraintTop_toBottomOf="@id/publish_dialog_message_layout"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintEnd_toEndOf="parent"
android:orientation="horizontal"
android:layout_marginTop="10dp"
android:baselineAligned="false">
<com.google.android.material.textfield.TextInputLayout
android:id="@+id/publish_dialog_tags_layout"
android:layout_width="0dp"
android:layout_height="wrap_content"
android:layout_weight="1"
android:layout_marginEnd="8dp">
<com.google.android.material.textfield.TextInputEditText
android:id="@+id/publish_dialog_tags_text"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:hint="@string/publish_dialog_tags_hint"
android:importantForAutofill="no"
android:maxLines="1"
android:inputType="text"/>
</com.google.android.material.textfield.TextInputLayout>
<com.google.android.material.textfield.TextInputLayout
style="@style/Widget.MaterialComponents.TextInputLayout.OutlinedBox.ExposedDropdownMenu"
android:id="@+id/publish_dialog_priority_layout"
android:layout_width="0dp"
android:layout_height="wrap_content"
android:layout_weight="1">
<AutoCompleteTextView
android:id="@+id/publish_dialog_priority_dropdown"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:inputType="none"
android:text="@string/publish_dialog_priority_default"/>
</com.google.android.material.textfield.TextInputLayout>
</LinearLayout>
<!-- Other features section -->
<TextView
android:id="@+id/publish_dialog_other_features_label"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginTop="20dp"
android:text="@string/publish_dialog_other_features"
android:textAppearance="@style/TextAppearance.Material3.LabelMedium"
android:textColor="?attr/colorOnSurfaceVariant"/>
<!-- Feature chips -->
<com.google.android.material.chip.ChipGroup
android:id="@+id/publish_dialog_chip_group"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_marginTop="8dp"
app:singleLine="false">
<com.google.android.material.chip.Chip
android:id="@+id/publish_dialog_chip_click_url"
style="@style/Widget.Material3.Chip.Filter"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="@string/publish_dialog_chip_click_url"/>
<com.google.android.material.chip.Chip
android:id="@+id/publish_dialog_chip_email"
style="@style/Widget.Material3.Chip.Filter"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="@string/publish_dialog_chip_email"/>
<com.google.android.material.chip.Chip
android:id="@+id/publish_dialog_chip_delay"
style="@style/Widget.Material3.Chip.Filter"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="@string/publish_dialog_chip_delay"/>
<com.google.android.material.chip.Chip
android:id="@+id/publish_dialog_chip_attach_url"
style="@style/Widget.Material3.Chip.Filter"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="@string/publish_dialog_chip_attach_url"/>
<com.google.android.material.chip.Chip
android:id="@+id/publish_dialog_chip_attach_file"
style="@style/Widget.Material3.Chip.Filter"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="@string/publish_dialog_chip_attach_file"/>
<com.google.android.material.chip.Chip
android:id="@+id/publish_dialog_chip_phone_call"
style="@style/Widget.Material3.Chip.Filter"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="@string/publish_dialog_chip_phone_call"/>
</com.google.android.material.chip.ChipGroup>
<!-- Click URL field -->
<LinearLayout
android:id="@+id/publish_dialog_click_url_layout"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:orientation="horizontal"
android:visibility="gone"
android:layout_marginTop="10dp"
android:gravity="center_vertical">
<com.google.android.material.textfield.TextInputLayout
android:layout_width="0dp"
android:layout_height="wrap_content"
android:layout_weight="1">
<com.google.android.material.textfield.TextInputEditText
android:id="@+id/publish_dialog_click_url_text"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:hint="@string/publish_dialog_click_url_hint"
android:importantForAutofill="no"
android:maxLines="1"
android:inputType="textUri"/>
</com.google.android.material.textfield.TextInputLayout>
<ImageButton
android:id="@+id/publish_dialog_click_url_remove"
android:layout_width="40dp"
android:layout_height="40dp"
android:src="@drawable/ic_cancel_gray_24dp"
android:background="?attr/selectableItemBackgroundBorderless"
android:contentDescription="@string/publish_dialog_remove_field"
app:tint="?attr/colorOnSurfaceVariant"/>
</LinearLayout>
<!-- Email field -->
<LinearLayout
android:id="@+id/publish_dialog_email_layout"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:orientation="horizontal"
android:visibility="gone"
android:layout_marginTop="10dp"
android:gravity="center_vertical">
<com.google.android.material.textfield.TextInputLayout
android:layout_width="0dp"
android:layout_height="wrap_content"
android:layout_weight="1">
<com.google.android.material.textfield.TextInputEditText
android:id="@+id/publish_dialog_email_text"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:hint="@string/publish_dialog_email_hint"
android:importantForAutofill="no"
android:maxLines="1"
android:inputType="textEmailAddress"/>
</com.google.android.material.textfield.TextInputLayout>
<ImageButton
android:id="@+id/publish_dialog_email_remove"
android:layout_width="40dp"
android:layout_height="40dp"
android:src="@drawable/ic_cancel_gray_24dp"
android:background="?attr/selectableItemBackgroundBorderless"
android:contentDescription="@string/publish_dialog_remove_field"
app:tint="?attr/colorOnSurfaceVariant"/>
</LinearLayout>
<!-- Delay field -->
<LinearLayout
android:id="@+id/publish_dialog_delay_layout"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:orientation="horizontal"
android:visibility="gone"
android:layout_marginTop="10dp"
android:gravity="center_vertical">
<com.google.android.material.textfield.TextInputLayout
android:layout_width="0dp"
android:layout_height="wrap_content"
android:layout_weight="1">
<com.google.android.material.textfield.TextInputEditText
android:id="@+id/publish_dialog_delay_text"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:hint="@string/publish_dialog_delay_hint"
android:importantForAutofill="no"
android:maxLines="1"
android:inputType="text"/>
</com.google.android.material.textfield.TextInputLayout>
<ImageButton
android:id="@+id/publish_dialog_delay_remove"
android:layout_width="40dp"
android:layout_height="40dp"
android:src="@drawable/ic_cancel_gray_24dp"
android:background="?attr/selectableItemBackgroundBorderless"
android:contentDescription="@string/publish_dialog_remove_field"
app:tint="?attr/colorOnSurfaceVariant"/>
</LinearLayout>
<!-- Attach URL fields -->
<LinearLayout
android:id="@+id/publish_dialog_attach_url_layout"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:orientation="vertical"
android:visibility="gone"
android:layout_marginTop="10dp">
<com.google.android.material.textfield.TextInputEditText
android:id="@+id/publish_dialog_tags_text"
<LinearLayout
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:hint="@string/publish_dialog_tags_hint"
android:importantForAutofill="no"
android:maxLines="1"
android:inputType="text"/>
android:orientation="horizontal"
android:gravity="center_vertical">
</com.google.android.material.textfield.TextInputLayout>
<com.google.android.material.textfield.TextInputLayout
android:layout_width="0dp"
android:layout_height="wrap_content"
android:layout_weight="1"
android:layout_marginEnd="8dp">
<com.google.android.material.textfield.TextInputLayout
style="@style/Widget.MaterialComponents.TextInputLayout.OutlinedBox.ExposedDropdownMenu"
android:id="@+id/publish_dialog_priority_layout"
<com.google.android.material.textfield.TextInputEditText
android:id="@+id/publish_dialog_attach_url_text"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:hint="@string/publish_dialog_attach_url_hint"
android:importantForAutofill="no"
android:maxLines="1"
android:inputType="textUri"/>
</com.google.android.material.textfield.TextInputLayout>
<com.google.android.material.textfield.TextInputLayout
android:layout_width="0dp"
android:layout_height="wrap_content"
android:layout_weight="1">
<com.google.android.material.textfield.TextInputEditText
android:id="@+id/publish_dialog_attach_filename_text"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:hint="@string/publish_dialog_attach_filename_hint"
android:importantForAutofill="no"
android:maxLines="1"
android:inputType="text"/>
</com.google.android.material.textfield.TextInputLayout>
<ImageButton
android:id="@+id/publish_dialog_attach_url_remove"
android:layout_width="40dp"
android:layout_height="40dp"
android:src="@drawable/ic_cancel_gray_24dp"
android:background="?attr/selectableItemBackgroundBorderless"
android:contentDescription="@string/publish_dialog_remove_field"
app:tint="?attr/colorOnSurfaceVariant"/>
</LinearLayout>
</LinearLayout>
<!-- Attach File field -->
<LinearLayout
android:id="@+id/publish_dialog_attach_file_layout"
android:layout_width="match_parent"
android:layout_height="wrap_content"
app:layout_constraintTop_toBottomOf="@id/publish_dialog_tags_layout"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintEnd_toEndOf="parent"
android:layout_marginTop="16dp">
<AutoCompleteTextView
android:id="@+id/publish_dialog_priority_dropdown"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:inputType="none"
android:text="@string/publish_dialog_priority_default"/>
</com.google.android.material.textfield.TextInputLayout>
<ImageView
android:id="@+id/publish_dialog_error_image"
android:layout_width="20dp"
android:layout_height="20dp"
android:layout_marginTop="1dp"
android:orientation="horizontal"
android:visibility="gone"
app:srcCompat="@drawable/ic_error_red_24dp"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toTopOf="@id/publish_dialog_error_text"/>
<TextView
android:id="@+id/publish_dialog_error_text"
android:layout_width="0dp"
android:layout_height="wrap_content"
android:layout_marginTop="10dp"
android:paddingStart="4dp"
android:paddingEnd="4dp"
android:textAppearance="@style/DangerText"
android:visibility="gone"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintTop_toBottomOf="@id/publish_dialog_priority_layout"
app:layout_constraintStart_toEndOf="@id/publish_dialog_error_image"/>
android:gravity="center_vertical">
</androidx.constraintlayout.widget.ConstraintLayout>
<com.google.android.material.button.MaterialButton
android:id="@+id/publish_dialog_attach_file_button"
style="@style/Widget.Material3.Button.OutlinedButton"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="@string/publish_dialog_attach_file_button"
app:icon="@drawable/ic_file_document_blue_24dp"/>
<TextView
android:id="@+id/publish_dialog_attach_file_name"
android:layout_width="0dp"
android:layout_height="wrap_content"
android:layout_weight="1"
android:layout_marginStart="12dp"
android:ellipsize="middle"
android:singleLine="true"
android:textColor="?attr/colorOnSurfaceVariant"/>
<ImageButton
android:id="@+id/publish_dialog_attach_file_remove"
android:layout_width="40dp"
android:layout_height="40dp"
android:src="@drawable/ic_cancel_gray_24dp"
android:background="?attr/selectableItemBackgroundBorderless"
android:contentDescription="@string/publish_dialog_remove_field"
app:tint="?attr/colorOnSurfaceVariant"/>
</LinearLayout>
<!-- Phone Call field -->
<LinearLayout
android:id="@+id/publish_dialog_phone_call_layout"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:orientation="horizontal"
android:visibility="gone"
android:layout_marginTop="10dp"
android:gravity="center_vertical">
<com.google.android.material.textfield.TextInputLayout
android:layout_width="0dp"
android:layout_height="wrap_content"
android:layout_weight="1">
<com.google.android.material.textfield.TextInputEditText
android:id="@+id/publish_dialog_phone_call_text"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:hint="@string/publish_dialog_phone_call_hint"
android:importantForAutofill="no"
android:maxLines="1"
android:inputType="phone"/>
</com.google.android.material.textfield.TextInputLayout>
<ImageButton
android:id="@+id/publish_dialog_phone_call_remove"
android:layout_width="40dp"
android:layout_height="40dp"
android:src="@drawable/ic_cancel_gray_24dp"
android:background="?attr/selectableItemBackgroundBorderless"
android:contentDescription="@string/publish_dialog_remove_field"
app:tint="?attr/colorOnSurfaceVariant"/>
</LinearLayout>
<!-- Documentation link -->
<TextView
android:id="@+id/publish_dialog_docs_link"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginTop="16dp"
android:layout_marginBottom="16dp"
android:text="@string/publish_dialog_docs_link"
android:textColor="?attr/colorPrimary"
android:textAppearance="@style/TextAppearance.Material3.BodySmall"/>
<!-- Error section -->
<LinearLayout
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:orientation="horizontal"
android:layout_marginTop="10dp"
android:gravity="center_vertical">
<ImageView
android:id="@+id/publish_dialog_error_image"
android:layout_width="20dp"
android:layout_height="20dp"
android:visibility="gone"
app:srcCompat="@drawable/ic_error_red_24dp"/>
<TextView
android:id="@+id/publish_dialog_error_text"
android:layout_width="0dp"
android:layout_height="wrap_content"
android:layout_weight="1"
android:paddingStart="4dp"
android:paddingEnd="4dp"
android:textAppearance="@style/DangerText"
android:visibility="gone"/>
</LinearLayout>
<!-- Bottom padding for keyboard -->
<View
android:layout_width="match_parent"
android:layout_height="100dp"/>
</LinearLayout>
</ScrollView>
</LinearLayout>
</androidx.coordinatorlayout.widget.CoordinatorLayout>

View file

@ -0,0 +1,29 @@
<?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:layout_width="match_parent"
android:layout_height="wrap_content"
android:orientation="horizontal"
android:gravity="center_vertical"
android:paddingHorizontal="16dp"
android:paddingVertical="12dp"
android:minHeight="48dp">
<ImageView
android:id="@+id/priority_icon"
android:layout_width="20dp"
android:layout_height="20dp"
android:layout_marginEnd="12dp"
android:contentDescription="@null"
app:srcCompat="@drawable/ic_priority_3_24dp"/>
<TextView
android:id="@+id/priority_text"
android:layout_width="0dp"
android:layout_height="wrap_content"
android:layout_weight="1"
android:textSize="16sp"
android:textColor="?android:attr/textColorPrimary"/>
</LinearLayout>

View file

@ -203,7 +203,7 @@
<string name="publish_dialog_title">Publish to %1$s</string>
<string name="publish_dialog_title_hint">Title (optional)</string>
<string name="publish_dialog_message_hint">Message</string>
<string name="publish_dialog_tags_hint">Tags (optional, comma-separated)</string>
<string name="publish_dialog_tags_hint">Tags</string>
<string name="publish_dialog_priority_default">Default priority</string>
<string name="publish_dialog_priority_min">Min priority</string>
<string name="publish_dialog_priority_low">Low priority</string>
@ -213,6 +213,23 @@
<string name="publish_dialog_button_send">Send</string>
<string name="publish_dialog_error_sending">Cannot send message: %1$s</string>
<string name="publish_dialog_message_published">Message published</string>
<string name="publish_dialog_format_markdown">Format as Markdown</string>
<string name="publish_dialog_other_features">Other features:</string>
<string name="publish_dialog_chip_click_url">Click URL</string>
<string name="publish_dialog_chip_email">Email</string>
<string name="publish_dialog_chip_delay">Delay</string>
<string name="publish_dialog_chip_attach_url">Attach by URL</string>
<string name="publish_dialog_chip_attach_file">Attach file</string>
<string name="publish_dialog_chip_phone_call">Phone call</string>
<string name="publish_dialog_click_url_hint">URL to open when notification is clicked</string>
<string name="publish_dialog_email_hint">Email address to forward to</string>
<string name="publish_dialog_delay_hint">Delay, e.g. 30m, 1h, tomorrow 9am</string>
<string name="publish_dialog_attach_url_hint">Attachment URL</string>
<string name="publish_dialog_attach_filename_hint">Filename</string>
<string name="publish_dialog_phone_call_hint">Phone number to call</string>
<string name="publish_dialog_attach_file_button">Pick file</string>
<string name="publish_dialog_remove_field">Remove field</string>
<string name="publish_dialog_docs_link">For examples and a detailed description of all send features, please refer to the documentation.</string>
<!-- Message bar -->
<string name="message_bar_hint">Type a message here</string>