From ffa2205a30e18fb3e5d5e2d14fc877ab50e3812b Mon Sep 17 00:00:00 2001 From: Codex Date: Mon, 9 Mar 2026 15:35:28 +0300 Subject: [PATCH] android: add coil and media3 cache foundation for media --- android/CHANGELOG.md | 5 +++ android/app/build.gradle.kts | 4 ++ .../messenger/MessengerApplication.kt | 28 +++++++++++++- .../messenger/di/MediaCacheModule.kt | 37 +++++++++++++++++++ docs/android-checklist.md | 2 +- 5 files changed, 74 insertions(+), 2 deletions(-) create mode 100644 android/app/src/main/java/ru/daemonlord/messenger/di/MediaCacheModule.kt diff --git a/android/CHANGELOG.md b/android/CHANGELOG.md index 06e362e..12ff6e4 100644 --- a/android/CHANGELOG.md +++ b/android/CHANGELOG.md @@ -353,3 +353,8 @@ - Implemented enqueue + optimistic behavior for `sendText`, `editMessage`, and `deleteMessage` on network failures. - Added automatic pending-action flush on chat sync/load-more and before new message operations. - Kept non-network server failures as immediate errors (no queueing), while allowing offline continuation. + +### Step 60 - Media cache foundation (Coil + Exo cache) +- Added global Coil image loader cache policy in `MessengerApplication` (memory + disk cache). +- Added Media3 `SimpleCache` singleton module for media stream/file caching foundation. +- Added Media3/Coil core dependencies and configured cache sizes for mobile usage. diff --git a/android/app/build.gradle.kts b/android/app/build.gradle.kts index 959a476..1261926 100644 --- a/android/app/build.gradle.kts +++ b/android/app/build.gradle.kts @@ -74,7 +74,11 @@ dependencies { implementation("androidx.compose.ui:ui:1.7.6") implementation("androidx.compose.ui:ui-tooling-preview:1.7.6") implementation("androidx.compose.material3:material3:1.3.1") + implementation("io.coil-kt:coil:2.7.0") implementation("io.coil-kt:coil-compose:2.7.0") + implementation("androidx.media3:media3-exoplayer:1.4.1") + implementation("androidx.media3:media3-datasource:1.4.1") + implementation("androidx.media3:media3-datasource-okhttp:1.4.1") implementation("org.jetbrains.kotlinx:kotlinx-coroutines-android:1.9.0") implementation("org.jetbrains.kotlinx:kotlinx-serialization-json:1.7.3") diff --git a/android/app/src/main/java/ru/daemonlord/messenger/MessengerApplication.kt b/android/app/src/main/java/ru/daemonlord/messenger/MessengerApplication.kt index 0b8a7c6..9ad958e 100644 --- a/android/app/src/main/java/ru/daemonlord/messenger/MessengerApplication.kt +++ b/android/app/src/main/java/ru/daemonlord/messenger/MessengerApplication.kt @@ -1,13 +1,39 @@ package ru.daemonlord.messenger import android.app.Application +import coil.ImageLoader +import coil.ImageLoaderFactory +import coil.disk.DiskCache +import coil.memory.MemoryCache import dagger.hilt.android.HiltAndroidApp import ru.daemonlord.messenger.core.notifications.NotificationChannels +import java.io.File @HiltAndroidApp -class MessengerApplication : Application() { +class MessengerApplication : Application(), ImageLoaderFactory { override fun onCreate() { super.onCreate() NotificationChannels.ensureCreated(this) } + + override fun newImageLoader(): ImageLoader { + val diskCacheDir = File(cacheDir, "coil_images") + if (!diskCacheDir.exists()) { + diskCacheDir.mkdirs() + } + return ImageLoader.Builder(this) + .memoryCache { + MemoryCache.Builder(this) + .maxSizePercent(0.25) + .build() + } + .diskCache { + DiskCache.Builder() + .directory(diskCacheDir) + .maxSizeBytes(250L * 1024L * 1024L) + .build() + } + .respectCacheHeaders(false) + .build() + } } diff --git a/android/app/src/main/java/ru/daemonlord/messenger/di/MediaCacheModule.kt b/android/app/src/main/java/ru/daemonlord/messenger/di/MediaCacheModule.kt new file mode 100644 index 0000000..aea98c3 --- /dev/null +++ b/android/app/src/main/java/ru/daemonlord/messenger/di/MediaCacheModule.kt @@ -0,0 +1,37 @@ +package ru.daemonlord.messenger.di + +import android.content.Context +import androidx.media3.database.StandaloneDatabaseProvider +import androidx.media3.datasource.cache.Cache +import androidx.media3.datasource.cache.LeastRecentlyUsedCacheEvictor +import androidx.media3.datasource.cache.SimpleCache +import dagger.Module +import dagger.Provides +import dagger.hilt.InstallIn +import dagger.hilt.android.qualifiers.ApplicationContext +import dagger.hilt.components.SingletonComponent +import java.io.File +import javax.inject.Singleton + +@Module +@InstallIn(SingletonComponent::class) +object MediaCacheModule { + + @Provides + @Singleton + fun provideMediaCache( + @ApplicationContext context: Context, + ): Cache { + val cacheDir = File(context.cacheDir, "exo_media_cache") + if (!cacheDir.exists()) { + cacheDir.mkdirs() + } + val maxBytes = 200L * 1024L * 1024L + return SimpleCache( + cacheDir, + LeastRecentlyUsedCacheEvictor(maxBytes), + StandaloneDatabaseProvider(context), + ) + } +} + diff --git a/docs/android-checklist.md b/docs/android-checklist.md index 121de81..2971504 100644 --- a/docs/android-checklist.md +++ b/docs/android-checklist.md @@ -20,7 +20,7 @@ ## 3. Локальное хранение и sync - [x] Room для чатов/сообщений/пользователей - [x] DataStore для настроек -- [ ] Кэш медиа (Coil/Exo cache) +- [x] Кэш медиа (Coil/Exo cache) - [x] Offline-first чтение истории - [x] Очередь отложенных действий (send/edit/delete) - [x] Конфликт-резолв и reconcile после reconnect