From 18261263ddc0027255e0676baa62366cd58e652f Mon Sep 17 00:00:00 2001
From: Philipp Heckel <pheckel@datto.com>
Date: Sun, 8 May 2022 20:41:17 -0400
Subject: [PATCH] Polish

---
 .../heckel/ntfy/ui/DetailSettingsActivity.kt  | 65 +++++++++++--------
 .../java/io/heckel/ntfy/ui/MainViewModel.kt   | 10 +++
 .../main/res/layout/fragment_main_item.xml    |  2 +-
 app/src/main/res/values/strings.xml           |  3 +-
 4 files changed, 51 insertions(+), 29 deletions(-)

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 8ef39a5..350398e 100644
--- a/app/src/main/java/io/heckel/ntfy/ui/DetailSettingsActivity.kt
+++ b/app/src/main/java/io/heckel/ntfy/ui/DetailSettingsActivity.kt
@@ -1,8 +1,10 @@
 package io.heckel.ntfy.ui
 
+import android.content.ContentResolver
 import android.graphics.BitmapFactory
 import android.net.Uri
 import android.os.Bundle
+import android.widget.Toast
 import androidx.activity.result.ActivityResultLauncher
 import androidx.activity.result.contract.ActivityResultContracts
 import androidx.appcompat.app.AppCompatActivity
@@ -67,6 +69,7 @@ class DetailSettingsActivity : AppCompatActivity() {
     }
 
     class SettingsFragment : PreferenceFragmentCompat() {
+        private lateinit var resolver: ContentResolver
         private lateinit var repository: Repository
         private lateinit var serviceManager: SubscriberServiceManager
         private lateinit var subscription: Subscription
@@ -81,6 +84,7 @@ class DetailSettingsActivity : AppCompatActivity() {
             // Dependencies (Fragments need a default constructor)
             repository = Repository.getInstance(requireActivity())
             serviceManager = SubscriberServiceManager(requireActivity())
+            resolver = requireContext().applicationContext.contentResolver
 
             // Create result launcher for custom icon (must be created in onCreatePreferences() directly)
             iconSetLauncher = createIconPickLauncher()
@@ -251,28 +255,26 @@ class DetailSettingsActivity : AppCompatActivity() {
         private fun loadIconRemovePref() {
             val prefId = context?.getString(R.string.detail_settings_appearance_icon_remove_key) ?: 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) {
                 try {
-                    val resolver = requireContext().applicationContext.contentResolver
                     val bitmapStream = resolver.openInputStream(Uri.parse(subscription.icon))
                     val bitmap = BitmapFactory.decodeStream(bitmapStream)
                     iconRemovePref.icon = bitmap.toDrawable(resources)
                 } 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> {
@@ -281,45 +283,54 @@ class DetailSettingsActivity : AppCompatActivity() {
                     return@registerForActivityResult
                 }
                 lifecycleScope.launch(Dispatchers.IO) {
+                    val outputUri = createUri() ?: return@launch
                     try {
                         // Write to cache storage
-                        val resolver = requireContext().applicationContext.contentResolver
                         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")
-                        inputStream.copyTo(outputStream)
-                        save(subscription.copy(icon = outputUri.toString()))
-
-                        // FIXME
-                        // FIXME
-
-                        iconSetPref.isVisible = false
+                        inputStream.use {
+                            it.copyTo(outputStream)
+                        }
 
+                        // Read image and set as preference icon
                         val bitmapStream = resolver.openInputStream(Uri.parse(outputUri.toString()))
                         val bitmap = BitmapFactory.decodeStream(bitmapStream)
+
+                        // Display "remove" preference
                         iconRemovePref.icon = bitmap.toDrawable(resources)
                         iconRemovePref.isVisible = true
+                        iconSetPref.isVisible = false
+
+                        // Finally, save (this is last!)
+                        save(subscription.copy(icon = outputUri.toString()))
                     } catch (e: Exception) {
                         Log.w(TAG, "Saving icon failed", e)
                         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)
             if (!dir.exists() && !dir.mkdirs()) {
-                throw Exception("Cannot create cache directory for attachments: $dir")
+                return null
             }
             val file =  File(dir, subscription.id.toString())
             return FileProvider.getUriForFile(requireContext(), DownloadWorker.FILE_PROVIDER_AUTHORITY, file)
         }
 
-        private fun loadBitmap() {
-            // FIXME
+        private fun deleteIcon(uri: String?) {
+            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) {
diff --git a/app/src/main/java/io/heckel/ntfy/ui/MainViewModel.kt b/app/src/main/java/io/heckel/ntfy/ui/MainViewModel.kt
index a0d17e6..6f5f446 100644
--- a/app/src/main/java/io/heckel/ntfy/ui/MainViewModel.kt
+++ b/app/src/main/java/io/heckel/ntfy/ui/MainViewModel.kt
@@ -1,12 +1,14 @@
 package io.heckel.ntfy.ui
 
 import android.content.Context
+import android.net.Uri
 import androidx.lifecycle.LiveData
 import androidx.lifecycle.ViewModel
 import androidx.lifecycle.ViewModelProvider
 import androidx.lifecycle.viewModelScope
 import io.heckel.ntfy.db.*
 import io.heckel.ntfy.up.Distributor
+import io.heckel.ntfy.util.Log
 import kotlinx.coroutines.Dispatchers
 import kotlinx.coroutines.launch
 import kotlin.collections.List
@@ -32,6 +34,14 @@ class SubscriptionsViewModel(private val repository: Repository) : ViewModel() {
         }
         repository.removeAllNotifications(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? {
diff --git a/app/src/main/res/layout/fragment_main_item.xml b/app/src/main/res/layout/fragment_main_item.xml
index 3106688..1b99936 100644
--- a/app/src/main/res/layout/fragment_main_item.xml
+++ b/app/src/main/res/layout/fragment_main_item.xml
@@ -12,7 +12,7 @@
             android:layout_height="35dp" app:srcCompat="@drawable/ic_sms_gray_24dp"
             android:id="@+id/main_item_image" app:layout_constraintTop_toTopOf="parent"
             app:layout_constraintStart_toStartOf="parent"
-            android:layout_marginTop="13dp"/>
+            android:layout_marginTop="17dp" android:scaleType="fitStart"/>
     <TextView
             android:text="ntfy.sh/example"
             android:layout_width="0dp"
diff --git a/app/src/main/res/values/strings.xml b/app/src/main/res/values/strings.xml
index e040048..0545d56 100644
--- a/app/src/main/res/values/strings.xml
+++ b/app/src/main/res/values/strings.xml
@@ -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_appearance_header">Appearance</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_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_suffix">global</string>