WIP subscription icon
This commit is contained in:
parent
2909d877f7
commit
a498d68bcf
6 changed files with 98 additions and 23 deletions
|
@ -81,7 +81,7 @@ dependencies {
|
|||
implementation 'com.squareup.okhttp3:okhttp:4.9.3'
|
||||
|
||||
// Firebase, sigh ... (only Google Play)
|
||||
playImplementation 'com.google.firebase:firebase-messaging:23.0.3'
|
||||
playImplementation 'com.google.firebase:firebase-messaging:23.0.4'
|
||||
|
||||
// RecyclerView
|
||||
implementation "androidx.recyclerview:recyclerview:1.3.0-alpha02"
|
||||
|
@ -90,7 +90,7 @@ dependencies {
|
|||
implementation 'androidx.swiperefreshlayout:swiperefreshlayout:1.1.0'
|
||||
|
||||
// Material design
|
||||
implementation "com.google.android.material:material:1.5.0"
|
||||
implementation "com.google.android.material:material:1.6.0"
|
||||
|
||||
// LiveData
|
||||
implementation "androidx.lifecycle:lifecycle-livedata-ktx:2.4.1"
|
||||
|
|
|
@ -1,23 +1,23 @@
|
|||
package io.heckel.ntfy.ui
|
||||
|
||||
import android.graphics.BitmapFactory
|
||||
import android.net.Uri
|
||||
import android.os.Bundle
|
||||
import androidx.activity.result.ActivityResultLauncher
|
||||
import androidx.activity.result.contract.ActivityResultContracts
|
||||
import androidx.appcompat.app.AppCompatActivity
|
||||
import androidx.core.content.FileProvider
|
||||
import androidx.core.graphics.drawable.toDrawable
|
||||
import androidx.lifecycle.lifecycleScope
|
||||
import androidx.preference.*
|
||||
import io.heckel.ntfy.BuildConfig
|
||||
import io.heckel.ntfy.R
|
||||
import io.heckel.ntfy.db.Notification
|
||||
import io.heckel.ntfy.db.Repository
|
||||
import io.heckel.ntfy.db.Subscription
|
||||
import io.heckel.ntfy.msg.DownloadWorker
|
||||
import io.heckel.ntfy.service.SubscriberServiceManager
|
||||
import io.heckel.ntfy.util.*
|
||||
import kotlinx.coroutines.*
|
||||
import okio.source
|
||||
import java.io.File
|
||||
import java.io.IOException
|
||||
import java.util.*
|
||||
|
@ -70,7 +70,10 @@ class DetailSettingsActivity : AppCompatActivity() {
|
|||
private lateinit var repository: Repository
|
||||
private lateinit var serviceManager: SubscriberServiceManager
|
||||
private lateinit var subscription: Subscription
|
||||
private lateinit var pickIconLauncher: ActivityResultLauncher<String>
|
||||
|
||||
private lateinit var iconSetPref: Preference
|
||||
private lateinit var iconSetLauncher: ActivityResultLauncher<String>
|
||||
private lateinit var iconRemovePref: Preference
|
||||
|
||||
override fun onCreatePreferences(savedInstanceState: Bundle?, rootKey: String?) {
|
||||
setPreferencesFromResource(R.xml.detail_preferences, rootKey)
|
||||
|
@ -80,7 +83,7 @@ class DetailSettingsActivity : AppCompatActivity() {
|
|||
serviceManager = SubscriberServiceManager(requireActivity())
|
||||
|
||||
// Create result launcher for custom icon (must be created in onCreatePreferences() directly)
|
||||
pickIconLauncher = createCustomIconPickLauncher()
|
||||
iconSetLauncher = createIconPickLauncher()
|
||||
|
||||
// Load subscription and users
|
||||
val subscriptionId = arguments?.getLong(DetailActivity.EXTRA_SUBSCRIPTION_ID) ?: return
|
||||
|
@ -99,7 +102,8 @@ class DetailSettingsActivity : AppCompatActivity() {
|
|||
loadMutedUntilPref()
|
||||
loadMinPriorityPref()
|
||||
loadAutoDeletePref()
|
||||
loadCustomIconsPref()
|
||||
loadIconSetPref()
|
||||
loadIconRemovePref()
|
||||
}
|
||||
|
||||
private fun loadInstantPref() {
|
||||
|
@ -233,41 +237,78 @@ class DetailSettingsActivity : AppCompatActivity() {
|
|||
}
|
||||
}
|
||||
|
||||
private fun loadCustomIconsPref() {
|
||||
val prefId = context?.getString(R.string.detail_settings_general_icon_key) ?: return
|
||||
val pref: Preference? = findPreference(prefId)
|
||||
pref?.isVisible = true // Hack: Show all settings at once, because subscription is loaded asynchronously
|
||||
pref?.preferenceDataStore = object : PreferenceDataStore() { } // Dummy store to protect from accidentally overwriting
|
||||
pref?.onPreferenceClickListener = Preference.OnPreferenceClickListener { _ ->
|
||||
pickIconLauncher.launch("image/*")
|
||||
false
|
||||
private fun loadIconSetPref() {
|
||||
val prefId = context?.getString(R.string.detail_settings_appearance_icon_set_key) ?: return
|
||||
iconSetPref = findPreference(prefId) ?: return
|
||||
iconSetPref.isVisible = subscription.icon == null
|
||||
iconSetPref.preferenceDataStore = object : PreferenceDataStore() { } // Dummy store to protect from accidentally overwriting
|
||||
iconSetPref.onPreferenceClickListener = Preference.OnPreferenceClickListener { _ ->
|
||||
iconSetLauncher.launch("image/*")
|
||||
true
|
||||
}
|
||||
}
|
||||
|
||||
private fun createCustomIconPickLauncher(): ActivityResultLauncher<String> {
|
||||
private fun loadIconRemovePref() {
|
||||
val prefId = context?.getString(R.string.detail_settings_appearance_icon_remove_key) ?: return
|
||||
iconRemovePref = findPreference(prefId) ?: return
|
||||
|
||||
// FIXME
|
||||
|
||||
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
|
||||
|
||||
}
|
||||
}
|
||||
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> {
|
||||
return registerForActivityResult(ActivityResultContracts.GetContent()) { inputUri ->
|
||||
if (inputUri == null) {
|
||||
return@registerForActivityResult
|
||||
}
|
||||
lifecycleScope.launch(Dispatchers.IO) {
|
||||
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
|
||||
|
||||
val bitmapStream = resolver.openInputStream(Uri.parse(outputUri.toString()))
|
||||
val bitmap = BitmapFactory.decodeStream(bitmapStream)
|
||||
iconRemovePref.icon = bitmap.toDrawable(resources)
|
||||
iconRemovePref.isVisible = true
|
||||
} catch (e: Exception) {
|
||||
Log.w(TAG, "Saving icon failed", e)
|
||||
requireActivity().runOnUiThread {
|
||||
// FIXME
|
||||
// FIXME TOAST
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
private fun createUri(): Uri {
|
||||
val dir = File(requireContext().cacheDir, SUBSCRIPTION_ICONS)
|
||||
if (!dir.exists() && !dir.mkdirs()) {
|
||||
|
@ -277,6 +318,10 @@ class DetailSettingsActivity : AppCompatActivity() {
|
|||
return FileProvider.getUriForFile(requireContext(), DownloadWorker.FILE_PROVIDER_AUTHORITY, file)
|
||||
}
|
||||
|
||||
private fun loadBitmap() {
|
||||
// FIXME
|
||||
}
|
||||
|
||||
private fun save(newSubscription: Subscription, refresh: Boolean = false) {
|
||||
subscription = newSubscription
|
||||
lifecycleScope.launch(Dispatchers.IO) {
|
||||
|
|
|
@ -1,9 +1,12 @@
|
|||
package io.heckel.ntfy.ui
|
||||
|
||||
import android.content.Context
|
||||
import android.graphics.BitmapFactory
|
||||
import android.net.Uri
|
||||
import android.view.LayoutInflater
|
||||
import android.view.View
|
||||
import android.view.ViewGroup
|
||||
import android.widget.ImageView
|
||||
import android.widget.TextView
|
||||
import androidx.recyclerview.widget.DiffUtil
|
||||
import androidx.recyclerview.widget.ListAdapter
|
||||
|
@ -13,6 +16,8 @@ import io.heckel.ntfy.R
|
|||
import io.heckel.ntfy.db.ConnectionState
|
||||
import io.heckel.ntfy.db.Repository
|
||||
import io.heckel.ntfy.db.Subscription
|
||||
import io.heckel.ntfy.msg.NotificationService
|
||||
import io.heckel.ntfy.util.Log
|
||||
import io.heckel.ntfy.util.topicShortUrl
|
||||
import java.text.DateFormat
|
||||
import java.util.*
|
||||
|
@ -47,6 +52,7 @@ class MainAdapter(private val repository: Repository, private val onClick: (Subs
|
|||
RecyclerView.ViewHolder(itemView) {
|
||||
private var subscription: Subscription? = null
|
||||
private val context: Context = itemView.context
|
||||
private val imageView: ImageView = itemView.findViewById(R.id.main_item_image)
|
||||
private val nameView: TextView = itemView.findViewById(R.id.main_item_text)
|
||||
private val statusView: TextView = itemView.findViewById(R.id.main_item_status)
|
||||
private val dateView: TextView = itemView.findViewById(R.id.main_item_date)
|
||||
|
@ -84,6 +90,16 @@ class MainAdapter(private val repository: Repository, private val onClick: (Subs
|
|||
val globalMutedUntil = repository.getGlobalMutedUntil()
|
||||
val showMutedForeverIcon = (subscription.mutedUntil == 1L || globalMutedUntil == 1L) && !isUnifiedPush
|
||||
val showMutedUntilIcon = !showMutedForeverIcon && (subscription.mutedUntil > 1L || globalMutedUntil > 1L) && !isUnifiedPush
|
||||
if (subscription.icon != null) {
|
||||
try {
|
||||
val resolver = context.applicationContext.contentResolver
|
||||
val bitmapStream = resolver.openInputStream(Uri.parse(subscription.icon))
|
||||
val bitmap = BitmapFactory.decodeStream(bitmapStream)
|
||||
imageView.setImageBitmap(bitmap)
|
||||
} catch (e: Exception) {
|
||||
Log.w(TAG, "Cannot load subscription icon", e)
|
||||
}
|
||||
}
|
||||
nameView.text = topicShortUrl(subscription.baseUrl, subscription.topic)
|
||||
statusView.text = statusMessage
|
||||
dateView.text = dateText
|
||||
|
@ -114,4 +130,8 @@ class MainAdapter(private val repository: Repository, private val onClick: (Subs
|
|||
return oldItem == newItem
|
||||
}
|
||||
}
|
||||
|
||||
companion object {
|
||||
const val TAG = "NtfyMainAdapter"
|
||||
}
|
||||
}
|
||||
|
|
|
@ -339,7 +339,10 @@
|
|||
<string name="detail_settings_notifications_instant_title">Instant delivery</string>
|
||||
<string name="detail_settings_notifications_instant_summary_on">Notifications are delivered instantly. Requires a foreground service and consumes more 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_general_icon_title">Custom icon</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_no_set">Set an icon to be displayed in notifications</string>
|
||||
<string name="detail_settings_global_setting_title">Use global setting</string>
|
||||
<string name="detail_settings_global_setting_suffix">global</string>
|
||||
|
||||
|
|
|
@ -35,7 +35,8 @@
|
|||
<string name="detail_settings_notifications_muted_until_key" translatable="false">SubscriptionMutedUntil</string>
|
||||
<string name="detail_settings_notifications_min_priority_key" translatable="false">SubscriptionMinPriority</string>
|
||||
<string name="detail_settings_notifications_auto_delete_key" translatable="false">SubscriptionAutoDelete</string>
|
||||
<string name="detail_settings_general_icon_key" translatable="false">SubscriptionIcon</string>
|
||||
<string name="detail_settings_appearance_icon_set_key" translatable="false">SubscriptionIconSet</string>
|
||||
<string name="detail_settings_appearance_icon_remove_key" translatable="false">SubscriptionIconRemove</string>
|
||||
|
||||
<!-- Main settings -->
|
||||
<string-array name="settings_notifications_muted_until_entries">
|
||||
|
|
|
@ -27,10 +27,16 @@
|
|||
app:defaultValue="-1"
|
||||
app:isPreferenceVisible="false"/> <!-- Same as Repository.AUTO_DELETE_USE_GLOBAL -->
|
||||
</PreferenceCategory>
|
||||
<PreferenceCategory app:title="@string/settings_general_header">
|
||||
<PreferenceCategory app:title="@string/detail_settings_appearance_header">
|
||||
<Preference
|
||||
app:key="@string/detail_settings_general_icon_key"
|
||||
app:title="@string/detail_settings_general_icon_title"
|
||||
app:key="@string/detail_settings_appearance_icon_set_key"
|
||||
app:title="@string/detail_settings_appearance_icon_title"
|
||||
app:summary="@string/detail_settings_appearance_icon_set_summary_no_set"
|
||||
app:isPreferenceVisible="false"/>
|
||||
<Preference
|
||||
app:key="@string/detail_settings_appearance_icon_remove_key"
|
||||
app:title="@string/detail_settings_appearance_icon_title"
|
||||
app:summary="@string/detail_settings_appearance_icon_set_summary_set"
|
||||
app:isPreferenceVisible="false"/>
|
||||
</PreferenceCategory>
|
||||
</PreferenceScreen>
|
||||
|
|
Loading…
Reference in a new issue