Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat(coil): enable very early support for animated emotes on iOS #453

Merged
merged 4 commits into from
Oct 4, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
@@ -1,26 +1,11 @@
package fr.outadoc.justchatting

import android.app.Application
import android.os.Build
import coil3.ImageLoader
import coil3.PlatformContext
import coil3.SingletonImageLoader
import coil3.disk.DiskCache
import coil3.gif.AnimatedImageDecoder
import coil3.gif.GifDecoder
import coil3.memory.MemoryCache
import coil3.request.crossfade
import coil3.request.transitionFactory
import coil3.transition.Transition
import coil3.util.DebugLogger
import com.google.android.material.color.DynamicColors
import fr.outadoc.justchatting.utils.logging.AndroidLogStrategy
import fr.outadoc.justchatting.utils.logging.Logger
import okio.Path.Companion.toOkioPath

class MainApplication :
Application(),
SingletonImageLoader.Factory {
class MainApplication : Application() {

override fun onCreate() {
super.onCreate()
Expand All @@ -31,33 +16,4 @@ class MainApplication :

DynamicColors.applyToActivitiesIfAvailable(this)
}

override fun newImageLoader(context: PlatformContext): ImageLoader =
ImageLoader.Builder(context)
.crossfade(true)
.memoryCache {
MemoryCache.Builder()
.maxSizePercent(applicationContext, percent = 0.25)
.build()
}
.diskCache {
DiskCache.Builder()
.directory(applicationContext.cacheDir.toOkioPath().resolve("image_cache"))
.maxSizePercent(0.02)
.build()
}
.transitionFactory(Transition.Factory.NONE)
.components {
if (Build.VERSION.SDK_INT >= 28) {
add(AnimatedImageDecoder.Factory(enforceMinimumFrameDelay = true))
} else {
add(GifDecoder.Factory())
}
}
.apply {
if (BuildConfig.ENABLE_LOGGING) {
logger(DebugLogger())
}
}
.build()
}
1 change: 1 addition & 0 deletions shared/build.gradle.kts
Original file line number Diff line number Diff line change
Expand Up @@ -113,6 +113,7 @@ kotlin {
implementation(libs.androidx.palette)
implementation(libs.androidx.paging.runtime.android)
implementation(libs.androidx.splashscreen)
implementation(libs.coil.gif)
implementation(libs.koin.android)
implementation(libs.ktor.client.okhttp)
implementation(libs.material.core)
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,40 @@
package fr.outadoc.justchatting.utils.coil

import android.os.Build
import coil3.ImageLoader
import coil3.PlatformContext
import coil3.SingletonImageLoader
import coil3.disk.DiskCache
import coil3.gif.AnimatedImageDecoder
import coil3.gif.GifDecoder
import coil3.memory.MemoryCache
import coil3.request.crossfade
import okio.Path.Companion.toOkioPath

internal actual object ImageLoaderFactory : SingletonImageLoader.Factory {

override fun newImageLoader(context: PlatformContext): ImageLoader {
return ImageLoader.Builder(context)
.crossfade(true)
.memoryCache {
MemoryCache.Builder()
.maxSizePercent(context, percent = 0.25)
.build()
}
.diskCache {
DiskCache.Builder()
.directory(context.cacheDir.toOkioPath().resolve("image_cache"))
.maxSizePercent(0.02)
.build()
}
.components {
if (Build.VERSION.SDK_INT >= 28) {
add(AnimatedImageDecoder.Factory())
} else {
add(GifDecoder.Factory())
}
}
.logger(CoilCustomLogger())
.build()
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -15,9 +15,11 @@ import androidx.compose.runtime.getValue
import androidx.compose.ui.Alignment
import androidx.compose.ui.Modifier
import androidx.navigation.compose.rememberNavController
import coil3.SingletonImageLoader
import com.eygraber.uri.Uri
import fr.outadoc.justchatting.feature.onboarding.presentation.mobile.OnboardingScreen
import fr.outadoc.justchatting.feature.shared.presentation.MainRouterViewModel
import fr.outadoc.justchatting.utils.coil.ImageLoaderFactory
import fr.outadoc.justchatting.utils.presentation.AppTheme
import fr.outadoc.justchatting.utils.presentation.OnLifecycleEvent
import org.koin.compose.koinInject
Expand All @@ -37,6 +39,11 @@ internal fun App(
val navController = rememberNavController()
val navigator = rememberListDetailPaneScaffoldNavigator<DetailScreen>()

LaunchedEffect(Unit) {
// Initialize Coil
SingletonImageLoader.setSafe(ImageLoaderFactory)
}

val onChannelClick = { userId: String ->
navigator.navigateTo(
pane = ListDetailPaneScaffoldRole.Detail,
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,37 @@
package fr.outadoc.justchatting.utils.coil

import fr.outadoc.justchatting.utils.logging.Logger

internal class CoilCustomLogger(
override var minLevel: coil3.util.Logger.Level = coil3.util.Logger.Level.Debug,
) : coil3.util.Logger {

override fun log(
tag: String,
level: coil3.util.Logger.Level,
message: String?,
throwable: Throwable?,
) {
Logger.println(
level = when (level) {
coil3.util.Logger.Level.Error -> Logger.Level.Error
coil3.util.Logger.Level.Warn -> Logger.Level.Warning
coil3.util.Logger.Level.Info -> Logger.Level.Info
coil3.util.Logger.Level.Debug -> Logger.Level.Debug
coil3.util.Logger.Level.Verbose -> Logger.Level.Verbose
},
tag = tag,
content = {
buildString {
if (message != null) {
appendLine(message)
}

if (throwable != null) {
appendLine(throwable.stackTraceToString())
}
}
},
)
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
package fr.outadoc.justchatting.utils.coil

import coil3.SingletonImageLoader

internal expect object ImageLoaderFactory : SingletonImageLoader.Factory
Original file line number Diff line number Diff line change
Expand Up @@ -30,6 +30,7 @@ import platform.Foundation.NSDocumentDirectory
import platform.Foundation.NSFileManager
import platform.Foundation.NSUserDomainMask

@OptIn(ExperimentalForeignApi::class)
internal actual val platformModule: Module
get() = module {
single {
Expand All @@ -49,25 +50,10 @@ internal actual val platformModule: Module
)
}

@OptIn(ExperimentalForeignApi::class)
single<DataStore<Preferences>> {
PreferenceDataStoreFactory.createWithPath(
produceFile = {
val documentDirectory: Path =
NSFileManager.defaultManager
.URLForDirectory(
directory = NSDocumentDirectory,
inDomain = NSUserDomainMask,
appropriateForURL = null,
create = false,
error = null,
)
?.path
?.toPath()
?: error("Could not get document directory")

documentDirectory
.resolve("fr.outadoc.justchatting.preferences_pb")
getDocumentsDirectory().resolve("fr.outadoc.justchatting.preferences_pb")
},
)
}
Expand All @@ -79,3 +65,18 @@ internal actual val platformModule: Module
single<AppVersionNameProvider> { AppleAppVersionNameProvider() }
single<ReadExternalDependenciesList> { AppleReadExternalDependenciesList() }
}

@OptIn(ExperimentalForeignApi::class)
private fun getDocumentsDirectory(): Path {
return NSFileManager.defaultManager
.URLForDirectory(
directory = NSDocumentDirectory,
inDomain = NSUserDomainMask,
appropriateForURL = null,
create = false,
error = null,
)
?.path
?.toPath()
?: error("Could not get document directory")
}
Loading
Loading