Loading app/src/main/java/io/heckel/ntfy/ui/DetailSettingsActivity.kt +38 −27 Original line number Diff line number Diff line 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 Loading Loading @@ -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 Loading @@ -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() Loading Loading @@ -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> { Loading @@ -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) { Loading app/src/main/java/io/heckel/ntfy/ui/MainViewModel.kt +10 −0 Original line number Diff line number Diff line 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 Loading @@ -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? { Loading app/src/main/res/layout/fragment_main_item.xml +1 −1 Original line number Diff line number Diff line Loading @@ -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" Loading app/src/main/res/values/strings.xml +2 −1 Original line number Diff line number Diff line Loading @@ -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> Loading Loading
app/src/main/java/io/heckel/ntfy/ui/DetailSettingsActivity.kt +38 −27 Original line number Diff line number Diff line 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 Loading Loading @@ -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 Loading @@ -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() Loading Loading @@ -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> { Loading @@ -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) { Loading
app/src/main/java/io/heckel/ntfy/ui/MainViewModel.kt +10 −0 Original line number Diff line number Diff line 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 Loading @@ -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? { Loading
app/src/main/res/layout/fragment_main_item.xml +1 −1 Original line number Diff line number Diff line Loading @@ -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" Loading
app/src/main/res/values/strings.xml +2 −1 Original line number Diff line number Diff line Loading @@ -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> Loading