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>