Base URL dropdown; working
This commit is contained in:
parent
29a40080db
commit
3dcf4939c8
5 changed files with 199 additions and 91 deletions
|
@ -20,9 +20,8 @@ import io.heckel.ntfy.BuildConfig
|
||||||
import io.heckel.ntfy.R
|
import io.heckel.ntfy.R
|
||||||
import io.heckel.ntfy.db.Repository
|
import io.heckel.ntfy.db.Repository
|
||||||
import io.heckel.ntfy.db.User
|
import io.heckel.ntfy.db.User
|
||||||
import io.heckel.ntfy.util.Log
|
|
||||||
import io.heckel.ntfy.msg.ApiService
|
import io.heckel.ntfy.msg.ApiService
|
||||||
import io.heckel.ntfy.util.topicUrl
|
import io.heckel.ntfy.util.*
|
||||||
import kotlinx.coroutines.Dispatchers
|
import kotlinx.coroutines.Dispatchers
|
||||||
import kotlinx.coroutines.launch
|
import kotlinx.coroutines.launch
|
||||||
|
|
||||||
|
@ -51,7 +50,6 @@ class AddFragment : DialogFragment() {
|
||||||
private lateinit var subscribeErrorTextImage: View
|
private lateinit var subscribeErrorTextImage: View
|
||||||
|
|
||||||
// Login page
|
// Login page
|
||||||
private lateinit var users: List<User>
|
|
||||||
private lateinit var loginUsernameText: TextInputEditText
|
private lateinit var loginUsernameText: TextInputEditText
|
||||||
private lateinit var loginPasswordText: TextInputEditText
|
private lateinit var loginPasswordText: TextInputEditText
|
||||||
private lateinit var loginProgress: ProgressBar
|
private lateinit var loginProgress: ProgressBar
|
||||||
|
@ -90,6 +88,7 @@ class AddFragment : DialogFragment() {
|
||||||
subscribeTopicText = view.findViewById(R.id.add_dialog_subscribe_topic_text)
|
subscribeTopicText = view.findViewById(R.id.add_dialog_subscribe_topic_text)
|
||||||
subscribeBaseUrlLayout = view.findViewById(R.id.add_dialog_subscribe_base_url_layout)
|
subscribeBaseUrlLayout = view.findViewById(R.id.add_dialog_subscribe_base_url_layout)
|
||||||
subscribeBaseUrlLayout.background = view.background
|
subscribeBaseUrlLayout.background = view.background
|
||||||
|
subscribeBaseUrlLayout.makeEndIconSmaller(resources) // Hack!
|
||||||
subscribeBaseUrlText = view.findViewById(R.id.add_dialog_subscribe_base_url_text)
|
subscribeBaseUrlText = view.findViewById(R.id.add_dialog_subscribe_base_url_text)
|
||||||
subscribeBaseUrlText.background = view.background
|
subscribeBaseUrlText.background = view.background
|
||||||
subscribeInstantDeliveryBox = view.findViewById(R.id.add_dialog_subscribe_instant_delivery_box)
|
subscribeInstantDeliveryBox = view.findViewById(R.id.add_dialog_subscribe_instant_delivery_box)
|
||||||
|
@ -103,13 +102,6 @@ class AddFragment : DialogFragment() {
|
||||||
subscribeErrorTextImage = view.findViewById(R.id.add_dialog_subscribe_error_text_image)
|
subscribeErrorTextImage = view.findViewById(R.id.add_dialog_subscribe_error_text_image)
|
||||||
subscribeErrorTextImage.visibility = View.GONE
|
subscribeErrorTextImage.visibility = View.GONE
|
||||||
|
|
||||||
// Hack: Make end icon smaller, see https://stackoverflow.com/a/57098715/1440785
|
|
||||||
val dimension = TypedValue.applyDimension(TypedValue.COMPLEX_UNIT_DIP, 30f, resources.displayMetrics)
|
|
||||||
val endIconImageView = subscribeBaseUrlLayout.findViewById<ImageView>(R.id.text_input_end_icon)
|
|
||||||
endIconImageView.minimumHeight = dimension.toInt()
|
|
||||||
endIconImageView.minimumWidth = dimension.toInt()
|
|
||||||
subscribeBaseUrlLayout.requestLayout()
|
|
||||||
|
|
||||||
// Fields for "login page"
|
// Fields for "login page"
|
||||||
loginUsernameText = view.findViewById(R.id.add_dialog_login_username)
|
loginUsernameText = view.findViewById(R.id.add_dialog_login_username)
|
||||||
loginPasswordText = view.findViewById(R.id.add_dialog_login_password)
|
loginPasswordText = view.findViewById(R.id.add_dialog_login_password)
|
||||||
|
@ -124,45 +116,11 @@ class AddFragment : DialogFragment() {
|
||||||
getString(R.string.add_dialog_use_another_server_description_noinstant)
|
getString(R.string.add_dialog_use_another_server_description_noinstant)
|
||||||
}
|
}
|
||||||
|
|
||||||
// Base URL dropdown behavior; Oh my, why is this so complicated?!
|
// Show/hide based on flavor
|
||||||
val toggleEndIcon = {
|
subscribeInstantDeliveryBox.visibility = if (BuildConfig.FIREBASE_AVAILABLE) View.VISIBLE else View.GONE
|
||||||
if (subscribeBaseUrlText.text.isNotEmpty()) {
|
|
||||||
subscribeBaseUrlLayout.setEndIconDrawable(R.drawable.ic_cancel_gray_24dp)
|
|
||||||
} else if (baseUrls.isEmpty()) {
|
|
||||||
subscribeBaseUrlLayout.setEndIconDrawable(0)
|
|
||||||
} else {
|
|
||||||
subscribeBaseUrlLayout.setEndIconDrawable(R.drawable.ic_drop_down_gray_24dp)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
subscribeBaseUrlLayout.setEndIconOnClickListener {
|
|
||||||
if (subscribeBaseUrlText.text.isNotEmpty()) {
|
|
||||||
subscribeBaseUrlText.text.clear()
|
|
||||||
if (baseUrls.isEmpty()) {
|
|
||||||
subscribeBaseUrlLayout.setEndIconDrawable(0)
|
|
||||||
} else {
|
|
||||||
subscribeBaseUrlLayout.setEndIconDrawable(R.drawable.ic_drop_down_gray_24dp)
|
|
||||||
}
|
|
||||||
} else if (subscribeBaseUrlText.text.isEmpty() && baseUrls.isNotEmpty()) {
|
|
||||||
subscribeBaseUrlLayout.setEndIconDrawable(R.drawable.ic_drop_up_gray_24dp)
|
|
||||||
subscribeBaseUrlText.showDropDown()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
subscribeBaseUrlText.setOnDismissListener { toggleEndIcon() }
|
|
||||||
subscribeBaseUrlText.addTextChangedListener(object : TextWatcher {
|
|
||||||
override fun afterTextChanged(s: Editable?) {
|
|
||||||
toggleEndIcon()
|
|
||||||
}
|
|
||||||
override fun onTextChanged(s: CharSequence?, start: Int, before: Int, count: Int) {
|
|
||||||
// Nothing
|
|
||||||
}
|
|
||||||
override fun beforeTextChanged(s: CharSequence?, start: Int, count: Int, after: Int) {
|
|
||||||
// Nothing
|
|
||||||
}
|
|
||||||
})
|
|
||||||
|
|
||||||
// Fill autocomplete for base URL & users drop-down
|
// Add baseUrl auto-complete behavior
|
||||||
lifecycleScope.launch(Dispatchers.IO) {
|
lifecycleScope.launch(Dispatchers.IO) {
|
||||||
// Auto-complete
|
|
||||||
val appBaseUrl = getString(R.string.app_base_url)
|
val appBaseUrl = getString(R.string.app_base_url)
|
||||||
baseUrls = repository.getSubscriptions()
|
baseUrls = repository.getSubscriptions()
|
||||||
.groupBy { it.baseUrl }
|
.groupBy { it.baseUrl }
|
||||||
|
@ -170,27 +128,11 @@ class AddFragment : DialogFragment() {
|
||||||
.filterNot { it == appBaseUrl }
|
.filterNot { it == appBaseUrl }
|
||||||
.sorted()
|
.sorted()
|
||||||
val activity = activity ?: return@launch // We may have pressed "Cancel"
|
val activity = activity ?: return@launch // We may have pressed "Cancel"
|
||||||
val adapter = ArrayAdapter(activity, R.layout.fragment_add_dialog_dropdown_item, baseUrls)
|
|
||||||
activity.runOnUiThread {
|
activity.runOnUiThread {
|
||||||
subscribeBaseUrlText.threshold = 1
|
initBaseUrlDropdown(baseUrls, subscribeBaseUrlText, subscribeBaseUrlLayout)
|
||||||
subscribeBaseUrlText.setAdapter(adapter)
|
|
||||||
if (baseUrls.count() == 1) {
|
|
||||||
subscribeBaseUrlLayout.setEndIconDrawable(R.drawable.ic_cancel_gray_24dp)
|
|
||||||
subscribeBaseUrlText.setText(baseUrls.first())
|
|
||||||
} else if (baseUrls.count() > 1) {
|
|
||||||
subscribeBaseUrlLayout.setEndIconDrawable(R.drawable.ic_drop_down_gray_24dp)
|
|
||||||
} else {
|
|
||||||
subscribeBaseUrlLayout.setEndIconDrawable(0)
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// Users dropdown
|
|
||||||
users = repository.getUsers()
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// Show/hide based on flavor
|
|
||||||
subscribeInstantDeliveryBox.visibility = if (BuildConfig.FIREBASE_AVAILABLE) View.VISIBLE else View.GONE
|
|
||||||
|
|
||||||
// Username/password validation on type
|
// Username/password validation on type
|
||||||
val loginTextWatcher = object : TextWatcher {
|
val loginTextWatcher = object : TextWatcher {
|
||||||
override fun afterTextChanged(s: Editable?) {
|
override fun afterTextChanged(s: Editable?) {
|
||||||
|
@ -384,13 +326,9 @@ class AddFragment : DialogFragment() {
|
||||||
if (subscription != null || DISALLOWED_TOPICS.contains(topic)) {
|
if (subscription != null || DISALLOWED_TOPICS.contains(topic)) {
|
||||||
positiveButton.isEnabled = false
|
positiveButton.isEnabled = false
|
||||||
} else if (subscribeUseAnotherServerCheckbox.isChecked) {
|
} else if (subscribeUseAnotherServerCheckbox.isChecked) {
|
||||||
positiveButton.isEnabled = topic.isNotBlank()
|
positiveButton.isEnabled = validTopic(topic) && validUrl(baseUrl)
|
||||||
&& "[-_A-Za-z0-9]{1,64}".toRegex().matches(topic)
|
|
||||||
&& baseUrl.isNotBlank()
|
|
||||||
&& "^https?://.+".toRegex().matches(baseUrl)
|
|
||||||
} else {
|
} else {
|
||||||
positiveButton.isEnabled = topic.isNotBlank()
|
positiveButton.isEnabled = validTopic(topic)
|
||||||
&& "[-_A-Za-z0-9]{1,64}".toRegex().matches(topic)
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
58
app/src/main/java/io/heckel/ntfy/ui/BaseUrl.kt
Normal file
58
app/src/main/java/io/heckel/ntfy/ui/BaseUrl.kt
Normal file
|
@ -0,0 +1,58 @@
|
||||||
|
package io.heckel.ntfy.ui
|
||||||
|
|
||||||
|
import android.text.Editable
|
||||||
|
import android.text.TextWatcher
|
||||||
|
import android.widget.ArrayAdapter
|
||||||
|
import android.widget.AutoCompleteTextView
|
||||||
|
import com.google.android.material.textfield.TextInputLayout
|
||||||
|
import io.heckel.ntfy.R
|
||||||
|
|
||||||
|
fun initBaseUrlDropdown(baseUrls: List<String>, textView: AutoCompleteTextView, layout: TextInputLayout) {
|
||||||
|
// Base URL dropdown behavior; Oh my, why is this so complicated?!
|
||||||
|
val toggleEndIcon = {
|
||||||
|
if (textView.text.isNotEmpty()) {
|
||||||
|
layout.setEndIconDrawable(R.drawable.ic_cancel_gray_24dp)
|
||||||
|
} else if (baseUrls.isEmpty()) {
|
||||||
|
layout.setEndIconDrawable(0)
|
||||||
|
} else {
|
||||||
|
layout.setEndIconDrawable(R.drawable.ic_drop_down_gray_24dp)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
layout.setEndIconOnClickListener {
|
||||||
|
if (textView.text.isNotEmpty()) {
|
||||||
|
textView.text.clear()
|
||||||
|
if (baseUrls.isEmpty()) {
|
||||||
|
layout.setEndIconDrawable(0)
|
||||||
|
} else {
|
||||||
|
layout.setEndIconDrawable(R.drawable.ic_drop_down_gray_24dp)
|
||||||
|
}
|
||||||
|
} else if (textView.text.isEmpty() && baseUrls.isNotEmpty()) {
|
||||||
|
layout.setEndIconDrawable(R.drawable.ic_drop_up_gray_24dp)
|
||||||
|
textView.showDropDown()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
textView.setOnDismissListener { toggleEndIcon() }
|
||||||
|
textView.addTextChangedListener(object : TextWatcher {
|
||||||
|
override fun afterTextChanged(s: Editable?) {
|
||||||
|
toggleEndIcon()
|
||||||
|
}
|
||||||
|
override fun onTextChanged(s: CharSequence?, start: Int, before: Int, count: Int) {
|
||||||
|
// Nothing
|
||||||
|
}
|
||||||
|
override fun beforeTextChanged(s: CharSequence?, start: Int, count: Int, after: Int) {
|
||||||
|
// Nothing
|
||||||
|
}
|
||||||
|
})
|
||||||
|
|
||||||
|
val adapter = ArrayAdapter(textView.context, R.layout.fragment_add_dialog_dropdown_item, baseUrls)
|
||||||
|
textView.threshold = 1
|
||||||
|
textView.setAdapter(adapter)
|
||||||
|
if (baseUrls.count() == 1) {
|
||||||
|
layout.setEndIconDrawable(R.drawable.ic_cancel_gray_24dp)
|
||||||
|
textView.setText(baseUrls.first())
|
||||||
|
} else if (baseUrls.count() > 1) {
|
||||||
|
layout.setEndIconDrawable(R.drawable.ic_drop_down_gray_24dp)
|
||||||
|
} else {
|
||||||
|
layout.setEndIconDrawable(0)
|
||||||
|
}
|
||||||
|
}
|
|
@ -10,12 +10,10 @@ import android.text.TextWatcher
|
||||||
import android.view.Menu
|
import android.view.Menu
|
||||||
import android.view.MenuItem
|
import android.view.MenuItem
|
||||||
import android.view.View
|
import android.view.View
|
||||||
import android.widget.ImageView
|
import android.widget.*
|
||||||
import android.widget.ProgressBar
|
|
||||||
import android.widget.TextView
|
|
||||||
import android.widget.Toast
|
|
||||||
import androidx.appcompat.app.AppCompatActivity
|
import androidx.appcompat.app.AppCompatActivity
|
||||||
import androidx.lifecycle.lifecycleScope
|
import androidx.lifecycle.lifecycleScope
|
||||||
|
import com.google.android.material.textfield.TextInputLayout
|
||||||
import io.heckel.ntfy.R
|
import io.heckel.ntfy.R
|
||||||
import io.heckel.ntfy.app.Application
|
import io.heckel.ntfy.app.Application
|
||||||
import io.heckel.ntfy.msg.ApiService
|
import io.heckel.ntfy.msg.ApiService
|
||||||
|
@ -30,6 +28,9 @@ class ShareActivity : AppCompatActivity() {
|
||||||
// File to share
|
// File to share
|
||||||
private var fileUri: Uri? = null
|
private var fileUri: Uri? = null
|
||||||
|
|
||||||
|
// List of base URLs used, excluding app_base_url
|
||||||
|
private lateinit var baseUrls: List<String>
|
||||||
|
|
||||||
// UI elements
|
// UI elements
|
||||||
private lateinit var menu: Menu
|
private lateinit var menu: Menu
|
||||||
private lateinit var sendItem: MenuItem
|
private lateinit var sendItem: MenuItem
|
||||||
|
@ -39,6 +40,9 @@ class ShareActivity : AppCompatActivity() {
|
||||||
private lateinit var contentFileIcon: ImageView
|
private lateinit var contentFileIcon: ImageView
|
||||||
private lateinit var contentText: TextView
|
private lateinit var contentText: TextView
|
||||||
private lateinit var topicText: TextView
|
private lateinit var topicText: TextView
|
||||||
|
private lateinit var baseUrlLayout: TextInputLayout
|
||||||
|
private lateinit var baseUrlText: AutoCompleteTextView
|
||||||
|
private lateinit var useAnotherServerCheckbox: CheckBox
|
||||||
private lateinit var progress: ProgressBar
|
private lateinit var progress: ProgressBar
|
||||||
private lateinit var errorText: TextView
|
private lateinit var errorText: TextView
|
||||||
private lateinit var errorImage: ImageView
|
private lateinit var errorImage: ImageView
|
||||||
|
@ -48,7 +52,7 @@ class ShareActivity : AppCompatActivity() {
|
||||||
setContentView(R.layout.activity_share)
|
setContentView(R.layout.activity_share)
|
||||||
|
|
||||||
Log.init(this) // Init logs in all entry points
|
Log.init(this) // Init logs in all entry points
|
||||||
Log.d(TAG, "Create $this")
|
Log.d(TAG, "Create $this with intent $intent")
|
||||||
|
|
||||||
// Action bar
|
// Action bar
|
||||||
title = getString(R.string.share_title)
|
title = getString(R.string.share_title)
|
||||||
|
@ -63,6 +67,12 @@ class ShareActivity : AppCompatActivity() {
|
||||||
contentFileInfo = findViewById(R.id.share_content_file_info)
|
contentFileInfo = findViewById(R.id.share_content_file_info)
|
||||||
contentFileIcon = findViewById(R.id.share_content_file_icon)
|
contentFileIcon = findViewById(R.id.share_content_file_icon)
|
||||||
topicText = findViewById(R.id.share_topic_text)
|
topicText = findViewById(R.id.share_topic_text)
|
||||||
|
baseUrlLayout = findViewById(R.id.share_base_url_layout)
|
||||||
|
//baseUrlLayout.background = window.background
|
||||||
|
baseUrlLayout.makeEndIconSmaller(resources) // Hack!
|
||||||
|
baseUrlText = findViewById(R.id.share_base_url_text)
|
||||||
|
//baseUrlText.background = topicText.background
|
||||||
|
useAnotherServerCheckbox = findViewById(R.id.share_use_another_server_checkbox)
|
||||||
progress = findViewById(R.id.share_progress)
|
progress = findViewById(R.id.share_progress)
|
||||||
progress.visibility = View.GONE
|
progress.visibility = View.GONE
|
||||||
errorText = findViewById(R.id.share_error_text)
|
errorText = findViewById(R.id.share_error_text)
|
||||||
|
@ -84,10 +94,31 @@ class ShareActivity : AppCompatActivity() {
|
||||||
contentText.addTextChangedListener(textWatcher)
|
contentText.addTextChangedListener(textWatcher)
|
||||||
topicText.addTextChangedListener(textWatcher)
|
topicText.addTextChangedListener(textWatcher)
|
||||||
|
|
||||||
|
// Add behavior to "use another" checkbox
|
||||||
|
useAnotherServerCheckbox.setOnCheckedChangeListener { _, isChecked ->
|
||||||
|
baseUrlLayout.visibility = if (isChecked) View.VISIBLE else View.GONE
|
||||||
|
validateInput()
|
||||||
|
}
|
||||||
|
|
||||||
|
// Add baseUrl auto-complete behavior
|
||||||
|
lifecycleScope.launch(Dispatchers.IO) {
|
||||||
|
val appBaseUrl = getString(R.string.app_base_url)
|
||||||
|
baseUrls = repository.getSubscriptions()
|
||||||
|
.groupBy { it.baseUrl }
|
||||||
|
.map { it.key }
|
||||||
|
.filterNot { it == appBaseUrl }
|
||||||
|
.sorted()
|
||||||
|
val activity = this@ShareActivity
|
||||||
|
activity.runOnUiThread {
|
||||||
|
initBaseUrlDropdown(baseUrls, baseUrlText, baseUrlLayout)
|
||||||
|
useAnotherServerCheckbox.isChecked = baseUrls.count() == 1
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
// Incoming intent
|
// Incoming intent
|
||||||
val intent = intent ?: return
|
val intent = intent ?: return
|
||||||
if (intent.action != Intent.ACTION_SEND) return
|
if (intent.action != Intent.ACTION_SEND) return
|
||||||
if ("text/plain" == intent.type) {
|
if (intent.type == "text/plain") {
|
||||||
handleSendText(intent)
|
handleSendText(intent)
|
||||||
} else if (supportedImage(intent.type)) {
|
} else if (supportedImage(intent.type)) {
|
||||||
handleSendImage(intent)
|
handleSendImage(intent)
|
||||||
|
@ -97,14 +128,19 @@ class ShareActivity : AppCompatActivity() {
|
||||||
}
|
}
|
||||||
|
|
||||||
private fun handleSendText(intent: Intent) {
|
private fun handleSendText(intent: Intent) {
|
||||||
intent.getStringExtra(Intent.EXTRA_TEXT)?.let { text ->
|
val text = intent.getStringExtra(Intent.EXTRA_TEXT) ?: "(no text)"
|
||||||
contentText.text = text
|
Log.d(TAG, "Shared content is text: $text")
|
||||||
show()
|
contentText.text = text
|
||||||
}
|
show()
|
||||||
}
|
}
|
||||||
|
|
||||||
private fun handleSendImage(intent: Intent) {
|
private fun handleSendImage(intent: Intent) {
|
||||||
fileUri = intent.getParcelableExtra<Parcelable>(Intent.EXTRA_STREAM) as? Uri ?: return
|
fileUri = intent.getParcelableExtra<Parcelable>(Intent.EXTRA_STREAM) as? Uri
|
||||||
|
Log.d(TAG, "Shared content is an image with URI $fileUri")
|
||||||
|
if (fileUri == null) {
|
||||||
|
Log.w(TAG, "Null URI is not allowed. Aborting.")
|
||||||
|
return
|
||||||
|
}
|
||||||
try {
|
try {
|
||||||
val resolver = applicationContext.contentResolver
|
val resolver = applicationContext.contentResolver
|
||||||
val bitmapStream = resolver.openInputStream(fileUri!!)
|
val bitmapStream = resolver.openInputStream(fileUri!!)
|
||||||
|
@ -121,7 +157,12 @@ class ShareActivity : AppCompatActivity() {
|
||||||
}
|
}
|
||||||
|
|
||||||
private fun handleSendFile(intent: Intent) {
|
private fun handleSendFile(intent: Intent) {
|
||||||
fileUri = intent.getParcelableExtra<Parcelable>(Intent.EXTRA_STREAM) as? Uri ?: return
|
fileUri = intent.getParcelableExtra<Parcelable>(Intent.EXTRA_STREAM) as? Uri
|
||||||
|
Log.d(TAG, "Shared content is a file with URI $fileUri")
|
||||||
|
if (fileUri == null) {
|
||||||
|
Log.w(TAG, "Null URI is not allowed. Aborting.")
|
||||||
|
return
|
||||||
|
}
|
||||||
try {
|
try {
|
||||||
val resolver = applicationContext.contentResolver
|
val resolver = applicationContext.contentResolver
|
||||||
val info = fileStat(this, fileUri)
|
val info = fileStat(this, fileUri)
|
||||||
|
@ -130,7 +171,6 @@ class ShareActivity : AppCompatActivity() {
|
||||||
contentFileInfo.text = "${info.filename}\n${formatBytes(info.size)}"
|
contentFileInfo.text = "${info.filename}\n${formatBytes(info.size)}"
|
||||||
contentFileIcon.setImageResource(mimeTypeToIconResource(mimeType))
|
contentFileIcon.setImageResource(mimeTypeToIconResource(mimeType))
|
||||||
show(file = true)
|
show(file = true)
|
||||||
|
|
||||||
} catch (e: Exception) {
|
} catch (e: Exception) {
|
||||||
fileUri = null
|
fileUri = null
|
||||||
contentText.text = ""
|
contentText.text = ""
|
||||||
|
@ -170,7 +210,7 @@ class ShareActivity : AppCompatActivity() {
|
||||||
}
|
}
|
||||||
|
|
||||||
private fun onShareClick() {
|
private fun onShareClick() {
|
||||||
val baseUrl = "https://ntfy.sh" // FIXME
|
val baseUrl = getBaseUrl()
|
||||||
val topic = topicText.text.toString()
|
val topic = topicText.text.toString()
|
||||||
val message = contentText.text.toString()
|
val message = contentText.text.toString()
|
||||||
progress.visibility = View.VISIBLE
|
progress.visibility = View.VISIBLE
|
||||||
|
@ -226,8 +266,21 @@ class ShareActivity : AppCompatActivity() {
|
||||||
|
|
||||||
private fun validateInput() {
|
private fun validateInput() {
|
||||||
if (!this::sendItem.isInitialized) return // Initialized late in onCreateOptionsMenu
|
if (!this::sendItem.isInitialized) return // Initialized late in onCreateOptionsMenu
|
||||||
sendItem.isEnabled = contentText.text.isNotEmpty() && topicText.text.isNotEmpty()
|
val enabled = if (useAnotherServerCheckbox.isChecked) {
|
||||||
sendItem.icon.alpha = if (sendItem.isEnabled) 255 else 130
|
contentText.text.isNotEmpty() && validTopic(topicText.text.toString()) && validUrl(baseUrlText.text.toString())
|
||||||
|
} else {
|
||||||
|
contentText.text.isNotEmpty() && topicText.text.isNotEmpty()
|
||||||
|
}
|
||||||
|
sendItem.isEnabled = enabled
|
||||||
|
sendItem.icon.alpha = if (enabled) 255 else 130
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun getBaseUrl(): String {
|
||||||
|
return if (useAnotherServerCheckbox.isChecked) {
|
||||||
|
baseUrlText.text.toString()
|
||||||
|
} else {
|
||||||
|
getString(R.string.app_base_url)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
companion object {
|
companion object {
|
||||||
|
|
|
@ -6,11 +6,15 @@ import android.content.ContentResolver
|
||||||
import android.content.Context
|
import android.content.Context
|
||||||
import android.content.res.Configuration
|
import android.content.res.Configuration
|
||||||
import android.content.res.Configuration.UI_MODE_NIGHT_YES
|
import android.content.res.Configuration.UI_MODE_NIGHT_YES
|
||||||
|
import android.content.res.Resources
|
||||||
import android.net.Uri
|
import android.net.Uri
|
||||||
import android.os.Build
|
import android.os.Build
|
||||||
import android.os.PowerManager
|
import android.os.PowerManager
|
||||||
import android.provider.OpenableColumns
|
import android.provider.OpenableColumns
|
||||||
|
import android.util.TypedValue
|
||||||
|
import android.view.View
|
||||||
import android.view.Window
|
import android.view.Window
|
||||||
|
import android.widget.ImageView
|
||||||
import androidx.appcompat.app.AppCompatDelegate
|
import androidx.appcompat.app.AppCompatDelegate
|
||||||
import io.heckel.ntfy.R
|
import io.heckel.ntfy.R
|
||||||
import io.heckel.ntfy.db.Notification
|
import io.heckel.ntfy.db.Notification
|
||||||
|
@ -40,6 +44,14 @@ fun shortUrl(url: String) = url
|
||||||
.replace("http://", "")
|
.replace("http://", "")
|
||||||
.replace("https://", "")
|
.replace("https://", "")
|
||||||
|
|
||||||
|
fun validTopic(topic: String): Boolean {
|
||||||
|
return "[-_A-Za-z0-9]{1,64}".toRegex().matches(topic) // Must match server side!
|
||||||
|
}
|
||||||
|
|
||||||
|
fun validUrl(url: String): Boolean {
|
||||||
|
return "^https?://.+".toRegex().matches(url)
|
||||||
|
}
|
||||||
|
|
||||||
fun formatDateShort(timestampSecs: Long): String {
|
fun formatDateShort(timestampSecs: Long): String {
|
||||||
val date = Date(timestampSecs*1000)
|
val date = Date(timestampSecs*1000)
|
||||||
return DateFormat.getDateTimeInstance(DateFormat.SHORT, DateFormat.SHORT).format(date)
|
return DateFormat.getDateTimeInstance(DateFormat.SHORT, DateFormat.SHORT).format(date)
|
||||||
|
@ -271,3 +283,12 @@ class ContentUriRequestBody(
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Hack: Make end icon for drop down smaller, see https://stackoverflow.com/a/57098715/1440785
|
||||||
|
fun View.makeEndIconSmaller(resources: Resources) {
|
||||||
|
val dimension = TypedValue.applyDimension(TypedValue.COMPLEX_UNIT_DIP, 30f, resources.displayMetrics)
|
||||||
|
val endIconImageView = findViewById<ImageView>(R.id.text_input_end_icon)
|
||||||
|
endIconImageView.minimumHeight = dimension.toInt()
|
||||||
|
endIconImageView.minimumWidth = dimension.toInt()
|
||||||
|
requestLayout()
|
||||||
|
}
|
||||||
|
|
|
@ -21,9 +21,9 @@
|
||||||
android:paddingBottom="2dp"
|
android:paddingBottom="2dp"
|
||||||
android:text="@string/share_content_title"
|
android:text="@string/share_content_title"
|
||||||
android:textAlignment="viewStart"
|
android:textAlignment="viewStart"
|
||||||
android:textAppearance="@style/TextAppearance.AppCompat.Medium"
|
android:textAppearance="@style/TextAppearance.Material3.TitleMedium"
|
||||||
app:layout_constraintStart_toStartOf="parent"
|
app:layout_constraintStart_toStartOf="parent"
|
||||||
app:layout_constraintTop_toTopOf="parent"/>
|
app:layout_constraintTop_toTopOf="parent" android:paddingStart="2dp"/>
|
||||||
<com.google.android.material.imageview.ShapeableImageView
|
<com.google.android.material.imageview.ShapeableImageView
|
||||||
android:layout_width="wrap_content"
|
android:layout_width="wrap_content"
|
||||||
android:layout_height="wrap_content" app:srcCompat="@drawable/ic_cancel_gray_24dp"
|
android:layout_height="wrap_content" app:srcCompat="@drawable/ic_cancel_gray_24dp"
|
||||||
|
@ -75,9 +75,9 @@
|
||||||
android:paddingBottom="3dp"
|
android:paddingBottom="3dp"
|
||||||
android:text="Share to topic"
|
android:text="Share to topic"
|
||||||
android:textAlignment="viewStart"
|
android:textAlignment="viewStart"
|
||||||
android:textAppearance="@style/TextAppearance.AppCompat.Medium"
|
android:textAppearance="@style/TextAppearance.Material3.TitleMedium"
|
||||||
app:layout_constraintStart_toStartOf="parent"
|
app:layout_constraintStart_toStartOf="parent"
|
||||||
app:layout_constraintTop_toBottomOf="@id/share_content_file_box" android:layout_marginTop="10dp"/>
|
app:layout_constraintTop_toBottomOf="@id/share_content_file_box" android:layout_marginTop="15dp" android:paddingStart="2dp"/>
|
||||||
<com.google.android.material.textfield.TextInputEditText
|
<com.google.android.material.textfield.TextInputEditText
|
||||||
android:id="@+id/share_topic_text"
|
android:id="@+id/share_topic_text"
|
||||||
android:layout_width="match_parent"
|
android:layout_width="match_parent"
|
||||||
|
@ -86,6 +86,44 @@
|
||||||
android:maxLines="1" android:inputType="text" android:maxLength="64"
|
android:maxLines="1" android:inputType="text" android:maxLength="64"
|
||||||
app:layout_constraintStart_toStartOf="parent" app:layout_constraintEnd_toEndOf="parent"
|
app:layout_constraintStart_toStartOf="parent" app:layout_constraintEnd_toEndOf="parent"
|
||||||
app:layout_constraintTop_toBottomOf="@id/share_topic_title"/>
|
app:layout_constraintTop_toBottomOf="@id/share_topic_title"/>
|
||||||
|
<CheckBox
|
||||||
|
android:text="@string/add_dialog_use_another_server"
|
||||||
|
android:layout_width="match_parent"
|
||||||
|
android:layout_height="wrap_content" android:id="@+id/share_use_another_server_checkbox"
|
||||||
|
android:layout_marginStart="-3dp" app:layout_constraintStart_toStartOf="parent"
|
||||||
|
app:layout_constraintEnd_toEndOf="parent"
|
||||||
|
app:layout_constraintTop_toBottomOf="@id/share_topic_text"/>
|
||||||
|
<com.google.android.material.textfield.TextInputLayout
|
||||||
|
style="@style/Widget.MaterialComponents.TextInputLayout.FilledBox.Dense.ExposedDropdownMenu"
|
||||||
|
android:id="@+id/share_base_url_layout"
|
||||||
|
android:layout_width="match_parent"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
android:layout_margin="0dp"
|
||||||
|
android:padding="0dp"
|
||||||
|
android:visibility="gone"
|
||||||
|
app:endIconMode="custom"
|
||||||
|
app:hintEnabled="false"
|
||||||
|
app:boxBackgroundColor="@null" app:layout_constraintStart_toStartOf="parent"
|
||||||
|
app:layout_constraintEnd_toEndOf="parent"
|
||||||
|
app:layout_constraintTop_toBottomOf="@id/share_use_another_server_checkbox">
|
||||||
|
<AutoCompleteTextView
|
||||||
|
android:layout_width="match_parent"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
android:id="@+id/share_base_url_text"
|
||||||
|
android:hint="@string/app_base_url"
|
||||||
|
android:maxLines="1"
|
||||||
|
android:layout_marginTop="0dp"
|
||||||
|
android:layout_marginBottom="0dp"
|
||||||
|
android:inputType="textNoSuggestions"
|
||||||
|
android:paddingStart="0dp"
|
||||||
|
android:paddingEnd="0dp"
|
||||||
|
android:paddingTop="5dp"
|
||||||
|
android:paddingBottom="5dp"
|
||||||
|
android:layout_marginStart="4dp"
|
||||||
|
android:layout_marginEnd="4dp"
|
||||||
|
android:textAppearance="?android:attr/textAppearanceMedium"
|
||||||
|
/>
|
||||||
|
</com.google.android.material.textfield.TextInputLayout>
|
||||||
<TextView
|
<TextView
|
||||||
android:text="Unable to resolve host example.com"
|
android:text="Unable to resolve host example.com"
|
||||||
android:layout_width="0dp"
|
android:layout_width="0dp"
|
||||||
|
@ -95,7 +133,7 @@
|
||||||
android:paddingEnd="4dp"
|
android:paddingEnd="4dp"
|
||||||
android:textAppearance="@style/DangerText"
|
android:textAppearance="@style/DangerText"
|
||||||
app:layout_constraintStart_toEndOf="@id/share_error_image"
|
app:layout_constraintStart_toEndOf="@id/share_error_image"
|
||||||
android:layout_marginTop="5dp" app:layout_constraintTop_toBottomOf="@+id/share_topic_text"/>
|
android:layout_marginTop="5dp" app:layout_constraintTop_toBottomOf="@id/share_base_url_layout"/>
|
||||||
<ImageView
|
<ImageView
|
||||||
android:layout_width="20dp"
|
android:layout_width="20dp"
|
||||||
android:layout_height="20dp" app:srcCompat="@drawable/ic_error_red_24dp"
|
android:layout_height="20dp" app:srcCompat="@drawable/ic_error_red_24dp"
|
||||||
|
|
Loading…
Reference in a new issue