This commit is contained in:
Philipp Heckel 2022-05-08 20:41:17 -04:00
parent a498d68bcf
commit 18261263dd
4 changed files with 51 additions and 29 deletions

View file

@ -1,8 +1,10 @@
package io.heckel.ntfy.ui package io.heckel.ntfy.ui
import android.content.ContentResolver
import android.graphics.BitmapFactory import android.graphics.BitmapFactory
import android.net.Uri import android.net.Uri
import android.os.Bundle import android.os.Bundle
import android.widget.Toast
import androidx.activity.result.ActivityResultLauncher import androidx.activity.result.ActivityResultLauncher
import androidx.activity.result.contract.ActivityResultContracts import androidx.activity.result.contract.ActivityResultContracts
import androidx.appcompat.app.AppCompatActivity import androidx.appcompat.app.AppCompatActivity
@ -67,6 +69,7 @@ class DetailSettingsActivity : AppCompatActivity() {
} }
class SettingsFragment : PreferenceFragmentCompat() { class SettingsFragment : PreferenceFragmentCompat() {
private lateinit var resolver: ContentResolver
private lateinit var repository: Repository private lateinit var repository: Repository
private lateinit var serviceManager: SubscriberServiceManager private lateinit var serviceManager: SubscriberServiceManager
private lateinit var subscription: Subscription private lateinit var subscription: Subscription
@ -81,6 +84,7 @@ class DetailSettingsActivity : AppCompatActivity() {
// Dependencies (Fragments need a default constructor) // Dependencies (Fragments need a default constructor)
repository = Repository.getInstance(requireActivity()) repository = Repository.getInstance(requireActivity())
serviceManager = SubscriberServiceManager(requireActivity()) serviceManager = SubscriberServiceManager(requireActivity())
resolver = requireContext().applicationContext.contentResolver
// Create result launcher for custom icon (must be created in onCreatePreferences() directly) // Create result launcher for custom icon (must be created in onCreatePreferences() directly)
iconSetLauncher = createIconPickLauncher() iconSetLauncher = createIconPickLauncher()
@ -251,28 +255,26 @@ class DetailSettingsActivity : AppCompatActivity() {
private fun loadIconRemovePref() { private fun loadIconRemovePref() {
val prefId = context?.getString(R.string.detail_settings_appearance_icon_remove_key) ?: return val prefId = context?.getString(R.string.detail_settings_appearance_icon_remove_key) ?: return
iconRemovePref = findPreference(prefId) ?: return iconRemovePref = findPreference(prefId) ?: return
iconRemovePref.isVisible = subscription.icon != null
iconRemovePref.preferenceDataStore = object : PreferenceDataStore() { } // Dummy store to protect from accidentally overwriting
iconRemovePref.onPreferenceClickListener = Preference.OnPreferenceClickListener { _ ->
iconRemovePref.isVisible = false
iconSetPref.isVisible = true
deleteIcon(subscription.icon)
save(subscription.copy(icon = null))
true
}
// FIXME // Set icon (if it exists)
if (subscription.icon != null) { if (subscription.icon != null) {
try { try {
val resolver = requireContext().applicationContext.contentResolver
val bitmapStream = resolver.openInputStream(Uri.parse(subscription.icon)) val bitmapStream = resolver.openInputStream(Uri.parse(subscription.icon))
val bitmap = BitmapFactory.decodeStream(bitmapStream) val bitmap = BitmapFactory.decodeStream(bitmapStream)
iconRemovePref.icon = bitmap.toDrawable(resources) iconRemovePref.icon = bitmap.toDrawable(resources)
} catch (e: Exception) { } catch (e: Exception) {
// FIXME Log.w(TAG, "Unable to set icon ${subscription.icon}", e)
} }
} }
iconRemovePref.isVisible = subscription.icon != null
iconRemovePref.preferenceDataStore = object : PreferenceDataStore() { } // Dummy store to protect from accidentally overwriting
iconRemovePref.onPreferenceClickListener = Preference.OnPreferenceClickListener { _ ->
save(subscription.copy(icon = null))
iconRemovePref.isVisible = false
iconSetPref.isVisible = true
true
}
} }
private fun createIconPickLauncher(): ActivityResultLauncher<String> { private fun createIconPickLauncher(): ActivityResultLauncher<String> {
@ -281,45 +283,54 @@ class DetailSettingsActivity : AppCompatActivity() {
return@registerForActivityResult return@registerForActivityResult
} }
lifecycleScope.launch(Dispatchers.IO) { lifecycleScope.launch(Dispatchers.IO) {
val outputUri = createUri() ?: return@launch
try { try {
// Write to cache storage // Write to cache storage
val resolver = requireContext().applicationContext.contentResolver
val inputStream = resolver.openInputStream(inputUri) ?: throw IOException("Couldn't open content URI for reading") val inputStream = resolver.openInputStream(inputUri) ?: throw IOException("Couldn't open content URI for reading")
val outputUri = createUri()
val outputStream = resolver.openOutputStream(outputUri) ?: throw IOException("Couldn't open content URI for writing") val outputStream = resolver.openOutputStream(outputUri) ?: throw IOException("Couldn't open content URI for writing")
inputStream.copyTo(outputStream) inputStream.use {
save(subscription.copy(icon = outputUri.toString())) it.copyTo(outputStream)
}
// FIXME
// FIXME
iconSetPref.isVisible = false
// Read image and set as preference icon
val bitmapStream = resolver.openInputStream(Uri.parse(outputUri.toString())) val bitmapStream = resolver.openInputStream(Uri.parse(outputUri.toString()))
val bitmap = BitmapFactory.decodeStream(bitmapStream) val bitmap = BitmapFactory.decodeStream(bitmapStream)
// Display "remove" preference
iconRemovePref.icon = bitmap.toDrawable(resources) iconRemovePref.icon = bitmap.toDrawable(resources)
iconRemovePref.isVisible = true iconRemovePref.isVisible = true
iconSetPref.isVisible = false
// Finally, save (this is last!)
save(subscription.copy(icon = outputUri.toString()))
} catch (e: Exception) { } catch (e: Exception) {
Log.w(TAG, "Saving icon failed", e) Log.w(TAG, "Saving icon failed", e)
requireActivity().runOnUiThread { requireActivity().runOnUiThread {
// FIXME TOAST Toast.makeText(context, getString(R.string.detail_settings_appearance_icon_error_saving, e.message), Toast.LENGTH_LONG).show()
} }
} }
} }
} }
} }
private fun createUri(): Uri { private fun createUri(): Uri? {
val dir = File(requireContext().cacheDir, SUBSCRIPTION_ICONS) val dir = File(requireContext().cacheDir, SUBSCRIPTION_ICONS)
if (!dir.exists() && !dir.mkdirs()) { if (!dir.exists() && !dir.mkdirs()) {
throw Exception("Cannot create cache directory for attachments: $dir") return null
} }
val file = File(dir, subscription.id.toString()) val file = File(dir, subscription.id.toString())
return FileProvider.getUriForFile(requireContext(), DownloadWorker.FILE_PROVIDER_AUTHORITY, file) return FileProvider.getUriForFile(requireContext(), DownloadWorker.FILE_PROVIDER_AUTHORITY, file)
} }
private fun loadBitmap() { private fun deleteIcon(uri: String?) {
// FIXME if (uri == null) {
return
}
try {
resolver.delete(Uri.parse(uri), null, null)
} catch (e: Exception) {
Log.w(TAG, "Unable to delete $uri", e)
}
} }
private fun save(newSubscription: Subscription, refresh: Boolean = false) { private fun save(newSubscription: Subscription, refresh: Boolean = false) {

View file

@ -1,12 +1,14 @@
package io.heckel.ntfy.ui package io.heckel.ntfy.ui
import android.content.Context import android.content.Context
import android.net.Uri
import androidx.lifecycle.LiveData import androidx.lifecycle.LiveData
import androidx.lifecycle.ViewModel import androidx.lifecycle.ViewModel
import androidx.lifecycle.ViewModelProvider import androidx.lifecycle.ViewModelProvider
import androidx.lifecycle.viewModelScope import androidx.lifecycle.viewModelScope
import io.heckel.ntfy.db.* import io.heckel.ntfy.db.*
import io.heckel.ntfy.up.Distributor import io.heckel.ntfy.up.Distributor
import io.heckel.ntfy.util.Log
import kotlinx.coroutines.Dispatchers import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.launch import kotlinx.coroutines.launch
import kotlin.collections.List import kotlin.collections.List
@ -32,6 +34,14 @@ class SubscriptionsViewModel(private val repository: Repository) : ViewModel() {
} }
repository.removeAllNotifications(subscriptionId) repository.removeAllNotifications(subscriptionId)
repository.removeSubscription(subscriptionId) repository.removeSubscription(subscriptionId)
if (subscription.icon != null) {
val resolver = context.applicationContext.contentResolver
try {
resolver.delete(Uri.parse(subscription.icon), null, null)
} catch (_: Exception) {
// Don't care
}
}
} }
suspend fun get(baseUrl: String, topic: String): Subscription? { suspend fun get(baseUrl: String, topic: String): Subscription? {

View file

@ -12,7 +12,7 @@
android:layout_height="35dp" app:srcCompat="@drawable/ic_sms_gray_24dp" android:layout_height="35dp" app:srcCompat="@drawable/ic_sms_gray_24dp"
android:id="@+id/main_item_image" app:layout_constraintTop_toTopOf="parent" android:id="@+id/main_item_image" app:layout_constraintTop_toTopOf="parent"
app:layout_constraintStart_toStartOf="parent" app:layout_constraintStart_toStartOf="parent"
android:layout_marginTop="13dp"/> android:layout_marginTop="17dp" android:scaleType="fitStart"/>
<TextView <TextView
android:text="ntfy.sh/example" android:text="ntfy.sh/example"
android:layout_width="0dp" android:layout_width="0dp"

View file

@ -341,8 +341,9 @@
<string name="detail_settings_notifications_instant_summary_off">Notifications are delivered using Firebase. Delivery may be delayed, but consumes less battery.</string> <string name="detail_settings_notifications_instant_summary_off">Notifications are delivered using Firebase. Delivery may be delayed, but consumes less battery.</string>
<string name="detail_settings_appearance_header">Appearance</string> <string name="detail_settings_appearance_header">Appearance</string>
<string name="detail_settings_appearance_icon_title">Subscription icon</string> <string name="detail_settings_appearance_icon_title">Subscription icon</string>
<string name="detail_settings_appearance_icon_set_summary_set">This icon is displayed in notifications. Tap to remove it.</string> <string name="detail_settings_appearance_icon_set_summary_set">This icon is displayed in notifications to this topic. Tap to remove it.</string>
<string name="detail_settings_appearance_icon_set_summary_no_set">Set an icon to be displayed in notifications</string> <string name="detail_settings_appearance_icon_set_summary_no_set">Set an icon to be displayed in notifications</string>
<string name="detail_settings_appearance_icon_error_saving">Unable to save icon: %1$s</string>
<string name="detail_settings_global_setting_title">Use global setting</string> <string name="detail_settings_global_setting_title">Use global setting</string>
<string name="detail_settings_global_setting_suffix">global</string> <string name="detail_settings_global_setting_suffix">global</string>