diff --git a/app/src/main/java/io/heckel/ntfy/db/Database.kt b/app/src/main/java/io/heckel/ntfy/db/Database.kt index b6622a12..3101ae2c 100644 --- a/app/src/main/java/io/heckel/ntfy/db/Database.kt +++ b/app/src/main/java/io/heckel/ntfy/db/Database.kt @@ -575,6 +575,9 @@ interface SubscriptionDao { """) fun getLastNotificationId(subscriptionIds: Collection): String? + @Query("UPDATE subscription SET icon = :icon WHERE id = :subscriptionId") + fun updateSubscriptionIcon(subscriptionId: Long, icon: String?) + @Query("DELETE FROM subscription WHERE id = :subscriptionId") fun remove(subscriptionId: Long) } diff --git a/app/src/main/java/io/heckel/ntfy/db/Repository.kt b/app/src/main/java/io/heckel/ntfy/db/Repository.kt index 4333180d..4de802d8 100644 --- a/app/src/main/java/io/heckel/ntfy/db/Repository.kt +++ b/app/src/main/java/io/heckel/ntfy/db/Repository.kt @@ -92,6 +92,10 @@ class Repository(private val sharedPrefs: SharedPreferences, database: Database) subscriptionDao.update(subscription) } + fun updateSubscriptionIcon(subscriptionId: Long, icon: String?) { + subscriptionDao.updateSubscriptionIcon(subscriptionId, icon) + } + @Suppress("RedundantSuspendModifier") @WorkerThread suspend fun removeSubscription(subscription: Subscription) { 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 f0293c79..577c3675 100644 --- a/app/src/main/java/io/heckel/ntfy/ui/DetailSettingsActivity.kt +++ b/app/src/main/java/io/heckel/ntfy/ui/DetailSettingsActivity.kt @@ -486,7 +486,7 @@ class DetailSettingsActivity : AppCompatActivity() { } private fun createUri(): Uri? { - val dir = File(requireContext().cacheDir, SUBSCRIPTION_ICONS) + val dir = File(requireContext().filesDir, SUBSCRIPTION_ICONS) if (!dir.exists() && !dir.mkdirs()) { return null } @@ -526,7 +526,6 @@ class DetailSettingsActivity : AppCompatActivity() { companion object { private const val TAG = "NtfyDetailSettingsActiv" - private const val SUBSCRIPTION_ICONS = "subscriptionIcons" private const val SUBSCRIPTION_ICON_MAX_SIZE_BYTES = 4194304 private const val SUBSCRIPTION_ICON_MAX_WIDTH = 2048 private const val SUBSCRIPTION_ICON_MAX_HEIGHT = 2048 diff --git a/app/src/main/java/io/heckel/ntfy/ui/MainActivity.kt b/app/src/main/java/io/heckel/ntfy/ui/MainActivity.kt index 4a1ceb87..6b6ff1fb 100644 --- a/app/src/main/java/io/heckel/ntfy/ui/MainActivity.kt +++ b/app/src/main/java/io/heckel/ntfy/ui/MainActivity.kt @@ -61,6 +61,7 @@ import io.heckel.ntfy.msg.Poller import io.heckel.ntfy.service.SubscriberService import io.heckel.ntfy.service.SubscriberServiceManager import io.heckel.ntfy.util.Log +import io.heckel.ntfy.util.SUBSCRIPTION_ICONS import io.heckel.ntfy.util.dangerButton import io.heckel.ntfy.util.displayName import io.heckel.ntfy.util.formatDateShort @@ -76,8 +77,10 @@ import io.heckel.ntfy.work.PollWorker import kotlinx.coroutines.Dispatchers import kotlinx.coroutines.delay import kotlinx.coroutines.launch +import java.io.File import java.util.Date import java.util.concurrent.TimeUnit +import androidx.core.content.FileProvider import androidx.core.view.size import androidx.core.view.get import androidx.core.net.toUri @@ -380,6 +383,9 @@ class MainActivity : AppCompatActivity(), AddFragment.SubscribeListener, Notific // Permissions maybeRequestNotificationPermission() + + // FIXME 2026-05-04: Remove this migration after 1 month + migrateSubscriptionIconsFromCache() } private fun maybeRequestNotificationPermission() { @@ -894,6 +900,38 @@ class MainActivity : AppCompatActivity(), AddFragment.SubscribeListener, Notific adapter.notifyItemRangeChanged(0, adapter.currentList.size) } + // FIXME 2026-05-04: Remove this migration after 1 month + private fun migrateSubscriptionIconsFromCache() { + lifecycleScope.launch(Dispatchers.IO) { + delay(5_000) // 5 seconds + try { + val oldDir = File(cacheDir, SUBSCRIPTION_ICONS) + if (!oldDir.exists() || !oldDir.isDirectory) return@launch + val newDir = File(filesDir, SUBSCRIPTION_ICONS) + if (!newDir.exists()) newDir.mkdirs() + oldDir.listFiles()?.forEach { oldFile -> + val newFile = File(newDir, oldFile.name) + if (newFile.exists()) { + oldFile.delete() + return@forEach + } + if (oldFile.renameTo(newFile)) { + val subscriptionId = oldFile.name.toLongOrNull() ?: return@forEach + val newUri = FileProvider.getUriForFile( + this@MainActivity, + BuildConfig.APPLICATION_ID + ".provider", + newFile + ) + repository.updateSubscriptionIcon(subscriptionId, newUri.toString()) + } + } + oldDir.delete() + } catch (e: Exception) { + Log.w(TAG, "Failed to migrate subscription icons", e) + } + } + } + companion object { const val TAG = "NtfyMainActivity" const val EXTRA_SUBSCRIPTION_ID = "subscriptionId" diff --git a/app/src/main/java/io/heckel/ntfy/util/Constants.kt b/app/src/main/java/io/heckel/ntfy/util/Constants.kt index d708011f..e2438962 100644 --- a/app/src/main/java/io/heckel/ntfy/util/Constants.kt +++ b/app/src/main/java/io/heckel/ntfy/util/Constants.kt @@ -1,6 +1,7 @@ package io.heckel.ntfy.util const val ANDROID_APP_MIME_TYPE = "application/vnd.android.package-archive" +const val SUBSCRIPTION_ICONS = "subscriptionIcons" const val PRIORITY_MIN = 1 const val PRIORITY_LOW = 2 diff --git a/app/src/main/res/xml/file_paths.xml b/app/src/main/res/xml/file_paths.xml index a5b6b209..a120c699 100644 --- a/app/src/main/res/xml/file_paths.xml +++ b/app/src/main/res/xml/file_paths.xml @@ -2,4 +2,5 @@ + diff --git a/fastlane/metadata/android/en-US/changelog/NEXT.txt b/fastlane/metadata/android/en-US/changelog/NEXT.txt index 9ddf0d28..e09a06af 100644 --- a/fastlane/metadata/android/en-US/changelog/NEXT.txt +++ b/fastlane/metadata/android/en-US/changelog/NEXT.txt @@ -4,3 +4,4 @@ Features: Bug fixes + maintenance: * Undo automatic phone number linking for numbers in message body (ntfy-android#170, thanks to @acortelyou for the contribution) +* Fix subscription icons disappearing after a few days due to Android clearing cache (#1322, thanks to @mcanning for reporting)