Snap for 14184262 from 54523667d4eb0c5c5bed5a50a6662c08f85be05d to androidx-compose-material3-adaptive-release

Change-Id: I5caeadf79a8c8dd190954a5dc55e278df643e9ae
diff --git a/app/build.gradle b/app/build.gradle
index eac44e2..6bb7c1d 100644
--- a/app/build.gradle
+++ b/app/build.gradle
@@ -1,10 +1,12 @@
+import androidx.build.KotlinTarget
+
 plugins {
     id("AndroidXPlugin")
     id("AndroidXComposePlugin")
     id("com.google.devtools.ksp")
     id("com.android.library")
     id("org.jetbrains.kotlin.android")
-    id("androidx.room")
+    id("androidx.room") version "2.7.0"
     alias(libs.plugins.kotlinSerialization)
 }
 
@@ -14,25 +16,41 @@
     compileSdk = 35
 
     defaultConfig {
-        // The schemas directory contains a schema file for each version of the Room database.
-        // This is required to enable Room auto migrations.
+        // Propagate the proguard rules to be used by the including app module
+        consumerProguardFiles("proguard-rules.pro")
+        // We don't export schemas, but Room's Gradle plugin doesn't know that. We use the project
+        // directory as a placeholder.
         room {
-            schemaDirectory("$projectDir/schemas")
+            schemaDirectory("$projectDir/room_schemas")
         }
     }
-
-    buildTypes {
-        release {
-            minifyEnabled false
-            proguardFiles("proguard-rules.pro")
-        }
-    }
-
-    buildFeatures {
-        buildConfig = true
-    }
 }
 
+
+// Create a release build type and make sure it's the only one enabled.
+// This is needed because we benchmark the release build type only.
+android.buildTypes { release {} }
+androidComponents { beforeVariants(selector().all()) { enabled = buildType == 'release' } }
+
+/**
+ * Exclusion groups for JetBrains Compose. When a dependency depends on JetBrains Compose, prebuilts
+ * for all other platforms will be pulled in. We also want to ensure we're using *Jetpack* Compose
+ * and not mix JetBrains and Jetpack Compose.
+ *
+ * IMPORTANT: When adding a dependency here, please make sure that it is otherwise present on the
+ * classpath.
+ */
+def jetbrainsComposeExcludeGroups = [
+        'org.jetbrains.androidx.lifecycle',
+        'org.jetbrains.compose.animation',
+        'org.jetbrains.compose.annotation-internal',
+        'org.jetbrains.compose.collection-internal',
+        'org.jetbrains.compose.foundation',
+        'org.jetbrains.compose.runtime',
+        'org.jetbrains.compose.ui',
+        'org.jetbrains.skiko'
+]
+
 dependencies {
     // Compose
     implementation(project(":compose:runtime:runtime"))
@@ -48,15 +66,26 @@
 
     // AndroidX
     implementation(libs.androidx.core)
-    implementation(libs.testRunner)
-    implementation("androidx.navigation:navigation-compose:2.8.2")
-    implementation("androidx.lifecycle:lifecycle-viewmodel-compose:2.8.4")
+    implementation("androidx.navigation:navigation-compose:2.7.7")
+    implementation("androidx.lifecycle:lifecycle-runtime:2.6.2")
+    implementation("androidx.lifecycle:lifecycle-viewmodel-compose:2.6.2")
+    implementation("androidx.lifecycle:lifecycle-runtime-compose:2.6.2")
     implementation("androidx.palette:palette:1.0.0")
 
     // Image Loading
     implementation("com.github.bumptech.glide:compose:1.0.0-beta01")
     implementation("com.github.bumptech.glide:okhttp3-integration:4.16.0")
     ksp("com.github.bumptech.glide:ksp:4.16.0")
+    implementation("io.coil-kt.coil3:coil-compose-android:3.1.0") {
+        jetbrainsComposeExcludeGroups.each { group ->
+            exclude group: group
+        }
+    }
+    implementation("io.coil-kt.coil3:coil-network-okhttp:3.1.0") {
+        jetbrainsComposeExcludeGroups.each { group ->
+            exclude group: group
+        }
+    }
 
     // Kotlinx
     implementation("org.jetbrains.kotlinx:kotlinx-collections-immutable:0.3.7")
@@ -66,13 +95,14 @@
     implementation(libs.kotlinCoroutinesAndroid)
 
     // Database
-    implementation("androidx.room:room-runtime:2.6.1")
-    implementation("androidx.room:room-ktx:2.6.1")
-    ksp("androidx.room:room-compiler:2.6.1")
+    implementation("androidx.room:room-runtime:2.7.0")
+    implementation("androidx.room:room-ktx:2.7.0")
+    ksp("androidx.room:room-compiler:2.7.0")
 
     // Network
     implementation("com.squareup.retrofit2:retrofit:2.11.0")
     implementation("com.squareup.retrofit2:converter-kotlinx-serialization:2.11.0")
+    api("com.squareup.okhttp3:okhttp:4.12.0")
     implementation("com.squareup.okhttp3:logging-interceptor:4.12.0")
     implementation("com.squareup.okhttp3:mockwebserver:4.12.0")
     implementation("com.squareup.okhttp3:okhttp-tls:4.12.0")
diff --git a/app/proguard-rules.pro b/app/proguard-rules.pro
index df35c17..0cf1f04 100644
--- a/app/proguard-rules.pro
+++ b/app/proguard-rules.pro
@@ -41,3 +41,21 @@
     public static **[] values();
     public static ** valueOf(java.lang.String);
 }
+
+# Serializer for classes with named companion objects are retrieved using `getDeclaredClasses`.
+# If you have any, replace classes with those containing named companion objects.
+-keepattributes InnerClasses # Needed for `getDeclaredClasses`.
+
+-if @kotlinx.serialization.Serializable class com.skydoves.pokedex.compose.core.model.Pokemon
+{
+    static **$* *;
+}
+-keepnames class <1>$$serializer { # -keepnames suffices; class is kept when serializer() is kept.
+    static <1>$$serializer INSTANCE;
+}
+
+# Keep both serializer and serializable classes to save the attribute InnerClasses
+-keepclasseswithmembers, allowshrinking, allowobfuscation, allowaccessmodification class com.skydoves.pokedex.compose.core.model.Pokemon
+{
+    *;
+}
diff --git a/app/room_schemas/.placeholder b/app/room_schemas/.placeholder
new file mode 100644
index 0000000..e69de29
--- /dev/null
+++ b/app/room_schemas/.placeholder
diff --git a/app/schemas/com.skydoves.pokedex.compose.core.database.PokedexDatabase/2.json b/app/schemas/com.skydoves.pokedex.compose.core.database.PokedexDatabase/2.json
deleted file mode 100644
index b8235b6..0000000
--- a/app/schemas/com.skydoves.pokedex.compose.core.database.PokedexDatabase/2.json
+++ /dev/null
@@ -1,126 +0,0 @@
-{
-  "formatVersion": 1,
-  "database": {
-    "version": 2,
-    "identityHash": "3e4fc349c7e47ef58902f587531caab0",
-    "entities": [
-      {
-        "tableName": "PokemonEntity",
-        "createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`page` INTEGER NOT NULL, `name` TEXT NOT NULL, `url` TEXT NOT NULL, PRIMARY KEY(`name`))",
-        "fields": [
-          {
-            "fieldPath": "page",
-            "columnName": "page",
-            "affinity": "INTEGER",
-            "notNull": true
-          },
-          {
-            "fieldPath": "name",
-            "columnName": "name",
-            "affinity": "TEXT",
-            "notNull": true
-          },
-          {
-            "fieldPath": "url",
-            "columnName": "url",
-            "affinity": "TEXT",
-            "notNull": true
-          }
-        ],
-        "primaryKey": {
-          "autoGenerate": false,
-          "columnNames": [
-            "name"
-          ]
-        },
-        "indices": [],
-        "foreignKeys": []
-      },
-      {
-        "tableName": "PokemonInfoEntity",
-        "createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`id` INTEGER NOT NULL, `name` TEXT NOT NULL, `height` INTEGER NOT NULL, `weight` INTEGER NOT NULL, `experience` INTEGER NOT NULL, `types` TEXT NOT NULL, `hp` INTEGER NOT NULL, `attack` INTEGER NOT NULL, `defense` INTEGER NOT NULL, `speed` INTEGER NOT NULL, `exp` INTEGER NOT NULL, PRIMARY KEY(`id`))",
-        "fields": [
-          {
-            "fieldPath": "id",
-            "columnName": "id",
-            "affinity": "INTEGER",
-            "notNull": true
-          },
-          {
-            "fieldPath": "name",
-            "columnName": "name",
-            "affinity": "TEXT",
-            "notNull": true
-          },
-          {
-            "fieldPath": "height",
-            "columnName": "height",
-            "affinity": "INTEGER",
-            "notNull": true
-          },
-          {
-            "fieldPath": "weight",
-            "columnName": "weight",
-            "affinity": "INTEGER",
-            "notNull": true
-          },
-          {
-            "fieldPath": "experience",
-            "columnName": "experience",
-            "affinity": "INTEGER",
-            "notNull": true
-          },
-          {
-            "fieldPath": "types",
-            "columnName": "types",
-            "affinity": "TEXT",
-            "notNull": true
-          },
-          {
-            "fieldPath": "hp",
-            "columnName": "hp",
-            "affinity": "INTEGER",
-            "notNull": true
-          },
-          {
-            "fieldPath": "attack",
-            "columnName": "attack",
-            "affinity": "INTEGER",
-            "notNull": true
-          },
-          {
-            "fieldPath": "defense",
-            "columnName": "defense",
-            "affinity": "INTEGER",
-            "notNull": true
-          },
-          {
-            "fieldPath": "speed",
-            "columnName": "speed",
-            "affinity": "INTEGER",
-            "notNull": true
-          },
-          {
-            "fieldPath": "exp",
-            "columnName": "exp",
-            "affinity": "INTEGER",
-            "notNull": true
-          }
-        ],
-        "primaryKey": {
-          "autoGenerate": false,
-          "columnNames": [
-            "id"
-          ]
-        },
-        "indices": [],
-        "foreignKeys": []
-      }
-    ],
-    "views": [],
-    "setupQueries": [
-      "CREATE TABLE IF NOT EXISTS room_master_table (id INTEGER PRIMARY KEY,identity_hash TEXT)",
-      "INSERT OR REPLACE INTO room_master_table (id,identity_hash) VALUES(42, '3e4fc349c7e47ef58902f587531caab0')"
-    ]
-  }
-}
\ No newline at end of file
diff --git a/app/schemas/com.skydoves.pokedex.compose.core.database.PokedexDatabase/3.json b/app/schemas/com.skydoves.pokedex.compose.core.database.PokedexDatabase/3.json
deleted file mode 100644
index ca4d915..0000000
--- a/app/schemas/com.skydoves.pokedex.compose.core.database.PokedexDatabase/3.json
+++ /dev/null
@@ -1,108 +0,0 @@
-{
-  "formatVersion": 1,
-  "database": {
-    "version": 3,
-    "identityHash": "2ce2c0e046fdc408aab83eb7e475bf26",
-    "entities": [
-      {
-        "tableName": "PokemonEntity",
-        "createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`page` INTEGER NOT NULL, `name` TEXT NOT NULL, `url` TEXT NOT NULL, PRIMARY KEY(`name`))",
-        "fields": [
-          {
-            "fieldPath": "page",
-            "columnName": "page",
-            "affinity": "INTEGER",
-            "notNull": true
-          },
-          {
-            "fieldPath": "name",
-            "columnName": "name",
-            "affinity": "TEXT",
-            "notNull": true
-          },
-          {
-            "fieldPath": "url",
-            "columnName": "url",
-            "affinity": "TEXT",
-            "notNull": true
-          }
-        ],
-        "primaryKey": {
-          "autoGenerate": false,
-          "columnNames": [
-            "name"
-          ]
-        },
-        "indices": [],
-        "foreignKeys": []
-      },
-      {
-        "tableName": "PokemonInfoEntity",
-        "createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`id` INTEGER NOT NULL, `name` TEXT NOT NULL, `height` INTEGER NOT NULL, `weight` INTEGER NOT NULL, `experience` INTEGER NOT NULL, `types` TEXT NOT NULL, `exp` INTEGER NOT NULL, `stats` TEXT NOT NULL, PRIMARY KEY(`id`))",
-        "fields": [
-          {
-            "fieldPath": "id",
-            "columnName": "id",
-            "affinity": "INTEGER",
-            "notNull": true
-          },
-          {
-            "fieldPath": "name",
-            "columnName": "name",
-            "affinity": "TEXT",
-            "notNull": true
-          },
-          {
-            "fieldPath": "height",
-            "columnName": "height",
-            "affinity": "INTEGER",
-            "notNull": true
-          },
-          {
-            "fieldPath": "weight",
-            "columnName": "weight",
-            "affinity": "INTEGER",
-            "notNull": true
-          },
-          {
-            "fieldPath": "experience",
-            "columnName": "experience",
-            "affinity": "INTEGER",
-            "notNull": true
-          },
-          {
-            "fieldPath": "types",
-            "columnName": "types",
-            "affinity": "TEXT",
-            "notNull": true
-          },
-          {
-            "fieldPath": "exp",
-            "columnName": "exp",
-            "affinity": "INTEGER",
-            "notNull": true
-          },
-          {
-            "fieldPath": "stats",
-            "columnName": "stats",
-            "affinity": "TEXT",
-            "notNull": true
-          }
-        ],
-        "primaryKey": {
-          "autoGenerate": false,
-          "columnNames": [
-            "id"
-          ]
-        },
-        "indices": [],
-        "foreignKeys": []
-      }
-    ],
-    "views": [],
-    "setupQueries": [
-      "CREATE TABLE IF NOT EXISTS room_master_table (id INTEGER PRIMARY KEY,identity_hash TEXT)",
-      "INSERT OR REPLACE INTO room_master_table (id,identity_hash) VALUES(42, '2ce2c0e046fdc408aab83eb7e475bf26')"
-    ]
-  }
-}
\ No newline at end of file
diff --git a/app/schemas/com.skydoves.pokedex.compose.core.database.PokedexDatabase/4.json b/app/schemas/com.skydoves.pokedex.compose.core.database.PokedexDatabase/4.json
deleted file mode 100644
index 7e92e13..0000000
--- a/app/schemas/com.skydoves.pokedex.compose.core.database.PokedexDatabase/4.json
+++ /dev/null
@@ -1,96 +0,0 @@
-{
-  "formatVersion": 1,
-  "database": {
-    "version": 4,
-    "identityHash": "24112c02c3e0f768491f43697e74e8ea",
-    "entities": [
-      {
-        "tableName": "PokemonEntity",
-        "createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`name` TEXT NOT NULL, PRIMARY KEY(`name`))",
-        "fields": [
-          {
-            "fieldPath": "name",
-            "columnName": "name",
-            "affinity": "TEXT",
-            "notNull": true
-          }
-        ],
-        "primaryKey": {
-          "autoGenerate": false,
-          "columnNames": [
-            "name"
-          ]
-        },
-        "indices": [],
-        "foreignKeys": []
-      },
-      {
-        "tableName": "PokemonInfoEntity",
-        "createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`id` INTEGER NOT NULL, `name` TEXT NOT NULL, `height` INTEGER NOT NULL, `weight` INTEGER NOT NULL, `experience` INTEGER NOT NULL, `types` TEXT NOT NULL, `exp` INTEGER NOT NULL, `stats` TEXT NOT NULL, PRIMARY KEY(`id`))",
-        "fields": [
-          {
-            "fieldPath": "id",
-            "columnName": "id",
-            "affinity": "INTEGER",
-            "notNull": true
-          },
-          {
-            "fieldPath": "name",
-            "columnName": "name",
-            "affinity": "TEXT",
-            "notNull": true
-          },
-          {
-            "fieldPath": "height",
-            "columnName": "height",
-            "affinity": "INTEGER",
-            "notNull": true
-          },
-          {
-            "fieldPath": "weight",
-            "columnName": "weight",
-            "affinity": "INTEGER",
-            "notNull": true
-          },
-          {
-            "fieldPath": "experience",
-            "columnName": "experience",
-            "affinity": "INTEGER",
-            "notNull": true
-          },
-          {
-            "fieldPath": "types",
-            "columnName": "types",
-            "affinity": "TEXT",
-            "notNull": true
-          },
-          {
-            "fieldPath": "exp",
-            "columnName": "exp",
-            "affinity": "INTEGER",
-            "notNull": true
-          },
-          {
-            "fieldPath": "stats",
-            "columnName": "stats",
-            "affinity": "TEXT",
-            "notNull": true
-          }
-        ],
-        "primaryKey": {
-          "autoGenerate": false,
-          "columnNames": [
-            "id"
-          ]
-        },
-        "indices": [],
-        "foreignKeys": []
-      }
-    ],
-    "views": [],
-    "setupQueries": [
-      "CREATE TABLE IF NOT EXISTS room_master_table (id INTEGER PRIMARY KEY,identity_hash TEXT)",
-      "INSERT OR REPLACE INTO room_master_table (id,identity_hash) VALUES(42, '24112c02c3e0f768491f43697e74e8ea')"
-    ]
-  }
-}
\ No newline at end of file
diff --git a/app/schemas/com.skydoves.pokedex.compose.core.database.PokedexDatabase/5.json b/app/schemas/com.skydoves.pokedex.compose.core.database.PokedexDatabase/5.json
deleted file mode 100644
index f9517ce..0000000
--- a/app/schemas/com.skydoves.pokedex.compose.core.database.PokedexDatabase/5.json
+++ /dev/null
@@ -1,96 +0,0 @@
-{
-  "formatVersion": 1,
-  "database": {
-    "version": 5,
-    "identityHash": "24112c02c3e0f768491f43697e74e8ea",
-    "entities": [
-      {
-        "tableName": "PokemonEntity",
-        "createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`name` TEXT NOT NULL, PRIMARY KEY(`name`))",
-        "fields": [
-          {
-            "fieldPath": "name",
-            "columnName": "name",
-            "affinity": "TEXT",
-            "notNull": true
-          }
-        ],
-        "primaryKey": {
-          "autoGenerate": false,
-          "columnNames": [
-            "name"
-          ]
-        },
-        "indices": [],
-        "foreignKeys": []
-      },
-      {
-        "tableName": "PokemonInfoEntity",
-        "createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`id` INTEGER NOT NULL, `name` TEXT NOT NULL, `height` INTEGER NOT NULL, `weight` INTEGER NOT NULL, `experience` INTEGER NOT NULL, `types` TEXT NOT NULL, `exp` INTEGER NOT NULL, `stats` TEXT NOT NULL, PRIMARY KEY(`id`))",
-        "fields": [
-          {
-            "fieldPath": "id",
-            "columnName": "id",
-            "affinity": "INTEGER",
-            "notNull": true
-          },
-          {
-            "fieldPath": "name",
-            "columnName": "name",
-            "affinity": "TEXT",
-            "notNull": true
-          },
-          {
-            "fieldPath": "height",
-            "columnName": "height",
-            "affinity": "INTEGER",
-            "notNull": true
-          },
-          {
-            "fieldPath": "weight",
-            "columnName": "weight",
-            "affinity": "INTEGER",
-            "notNull": true
-          },
-          {
-            "fieldPath": "experience",
-            "columnName": "experience",
-            "affinity": "INTEGER",
-            "notNull": true
-          },
-          {
-            "fieldPath": "types",
-            "columnName": "types",
-            "affinity": "TEXT",
-            "notNull": true
-          },
-          {
-            "fieldPath": "exp",
-            "columnName": "exp",
-            "affinity": "INTEGER",
-            "notNull": true
-          },
-          {
-            "fieldPath": "stats",
-            "columnName": "stats",
-            "affinity": "TEXT",
-            "notNull": true
-          }
-        ],
-        "primaryKey": {
-          "autoGenerate": false,
-          "columnNames": [
-            "id"
-          ]
-        },
-        "indices": [],
-        "foreignKeys": []
-      }
-    ],
-    "views": [],
-    "setupQueries": [
-      "CREATE TABLE IF NOT EXISTS room_master_table (id INTEGER PRIMARY KEY,identity_hash TEXT)",
-      "INSERT OR REPLACE INTO room_master_table (id,identity_hash) VALUES(42, '24112c02c3e0f768491f43697e74e8ea')"
-    ]
-  }
-}
\ No newline at end of file
diff --git a/app/src/main/kotlin/com/skydoves/pokedex/compose/core/PokedexFeatureFlags.kt b/app/src/main/kotlin/com/skydoves/pokedex/compose/core/PokedexFeatureFlags.kt
new file mode 100644
index 0000000..b8acea8
--- /dev/null
+++ b/app/src/main/kotlin/com/skydoves/pokedex/compose/core/PokedexFeatureFlags.kt
@@ -0,0 +1,39 @@
+/*
+ * Copyright 2025 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.skydoves.pokedex.compose.core
+
+/** Contains feature flags for the Pokedex hero benchmark target */
+object PokedexFeatureFlags {
+    /**
+     * Whether to configure and use Coil for image loading of all images, or Glide if false. Please
+     * note that Glide will always be configured.
+     */
+    var UseCoil = true
+
+    /**
+     * Whether [androidx.compose.animation.SharedTransitionScope] should be used or replaced by
+     * simpler layouts instead. If false, shared element transitions will be off too.
+     */
+    var EnableSharedTransitionScope = true
+
+    /**
+     * Whether to enable shared element transitions between the activities.
+     * [EnableSharedTransitionScope] must be set to true, otherwise this flag will be false.
+     */
+    var EnableSharedElementTransitions = true
+        get() = EnableSharedTransitionScope && field
+}
diff --git a/app/src/main/kotlin/com/skydoves/pokedex/compose/core/data/repository/details/DetailsRepositoryImpl.kt b/app/src/main/kotlin/com/skydoves/pokedex/compose/core/data/repository/details/DetailsRepositoryImpl.kt
index 91991ca..2612f66 100644
--- a/app/src/main/kotlin/com/skydoves/pokedex/compose/core/data/repository/details/DetailsRepositoryImpl.kt
+++ b/app/src/main/kotlin/com/skydoves/pokedex/compose/core/data/repository/details/DetailsRepositoryImpl.kt
@@ -18,8 +18,8 @@
 
 import androidx.annotation.WorkerThread
 import com.skydoves.pokedex.compose.core.database.PokemonInfoDao
-import com.skydoves.pokedex.compose.core.database.entitiy.mapper.asDomain
-import com.skydoves.pokedex.compose.core.database.entitiy.mapper.asEntity
+import com.skydoves.pokedex.compose.core.database.entitiy.mapper.asDatabaseEntity
+import com.skydoves.pokedex.compose.core.database.entitiy.mapper.asPresentationModel
 import com.skydoves.pokedex.compose.core.network.Dispatcher
 import com.skydoves.pokedex.compose.core.network.PokedexAppDispatchers
 import com.skydoves.pokedex.compose.core.network.service.PokedexClient
@@ -38,7 +38,7 @@
     override fun fetchPokemonInfo(
         name: String,
         onComplete: () -> Unit,
-        onError: (String?) -> Unit
+        onError: (String?) -> Unit,
     ) =
         flow {
                 val pokemonInfo = pokemonInfoDao.getPokemonInfo(name)
@@ -46,12 +46,12 @@
                     val response = pokedexClient.fetchPokemonInfo(name = name)
                     response
                         .onSuccess { data ->
-                            pokemonInfoDao.insertPokemonInfo(data.asEntity())
+                            pokemonInfoDao.insertPokemonInfo(data.asDatabaseEntity())
                             emit(data)
                         }
                         .onFailure { throwable -> onError(throwable.message) }
                 } else {
-                    emit(pokemonInfo.asDomain())
+                    emit(pokemonInfo.asPresentationModel())
                 }
             }
             .onCompletion { onComplete() }
diff --git a/app/src/main/kotlin/com/skydoves/pokedex/compose/core/data/repository/home/HomeRepositoryImpl.kt b/app/src/main/kotlin/com/skydoves/pokedex/compose/core/data/repository/home/HomeRepositoryImpl.kt
index b347870..05471a5 100644
--- a/app/src/main/kotlin/com/skydoves/pokedex/compose/core/data/repository/home/HomeRepositoryImpl.kt
+++ b/app/src/main/kotlin/com/skydoves/pokedex/compose/core/data/repository/home/HomeRepositoryImpl.kt
@@ -34,7 +34,7 @@
     private val pokedexClient: PokedexClient,
     private val pokemonDao: PokemonDao,
     @Dispatcher(PokedexAppDispatchers.IO) private val ioDispatcher: CoroutineDispatcher,
-    private val apiUrl: HttpUrl
+    private val apiUrl: HttpUrl,
 ) : HomeRepository {
 
     @WorkerThread
diff --git a/app/src/main/kotlin/com/skydoves/pokedex/compose/core/database/PokedexDatabase.kt b/app/src/main/kotlin/com/skydoves/pokedex/compose/core/database/PokedexDatabase.kt
index 3ba123c..2cb1821 100644
--- a/app/src/main/kotlin/com/skydoves/pokedex/compose/core/database/PokedexDatabase.kt
+++ b/app/src/main/kotlin/com/skydoves/pokedex/compose/core/database/PokedexDatabase.kt
@@ -24,8 +24,8 @@
 
 @Database(
     entities = [PokemonEntity::class, PokemonInfoEntity::class],
-    version = 5,
-    exportSchema = true,
+    version = 1,
+    exportSchema = false, /* We don't require schema versioning in Hero Benchmarks */
 )
 @TypeConverters(value = [TypeResponseConverter::class, StatsResponseConverter::class])
 abstract class PokedexDatabase : RoomDatabase() {
diff --git a/app/src/main/kotlin/com/skydoves/pokedex/compose/core/database/entitiy/mapper/PokemonEntityMapper.kt b/app/src/main/kotlin/com/skydoves/pokedex/compose/core/database/entitiy/mapper/PokemonEntityMapper.kt
index c61d20e..edd3012 100644
--- a/app/src/main/kotlin/com/skydoves/pokedex/compose/core/database/entitiy/mapper/PokemonEntityMapper.kt
+++ b/app/src/main/kotlin/com/skydoves/pokedex/compose/core/database/entitiy/mapper/PokemonEntityMapper.kt
@@ -19,6 +19,7 @@
 import com.skydoves.pokedex.compose.core.database.entitiy.PokemonEntity
 import com.skydoves.pokedex.compose.core.model.Pokemon
 import com.skydoves.pokedex.compose.core.model.PokemonNetworkModel
+import com.skydoves.pokedex.compose.core.network.di.ModuleLocator
 import okhttp3.HttpUrl
 
 fun List<PokemonNetworkModel>.asDatabaseEntity(): List<PokemonEntity> = map { pokemon ->
@@ -29,14 +30,17 @@
     map { entity ->
         Pokemon(
             name = entity.name.replaceFirstChar { it.uppercase() },
-            imageUrl =
-                apiUrl
-                    .newBuilder()
-                    .addPathSegment("pokemon")
-                    .addPathSegment(entity.name)
-                    .addPathSegment("image")
-                    .build()
-                    .toString(),
-            page = page
+            imageUrl = getPokemonImageUrlByName(name = entity.name, apiUrl = apiUrl).toString(),
+            page = page,
         )
     }
+
+fun getPokemonImageUrlByName(name: String, apiUrl: HttpUrl? = null): HttpUrl {
+    val baseApiUrl = apiUrl ?: ModuleLocator.networkModule.baseUrl
+    return baseApiUrl
+        .newBuilder()
+        .addPathSegment("pokemon")
+        .addPathSegment(name)
+        .addPathSegment("image")
+        .build()
+}
diff --git a/app/src/main/kotlin/com/skydoves/pokedex/compose/core/database/entitiy/mapper/PokemonInfoEntityMapper.kt b/app/src/main/kotlin/com/skydoves/pokedex/compose/core/database/entitiy/mapper/PokemonInfoEntityMapper.kt
index 7c105d2..e2bedd3 100644
--- a/app/src/main/kotlin/com/skydoves/pokedex/compose/core/database/entitiy/mapper/PokemonInfoEntityMapper.kt
+++ b/app/src/main/kotlin/com/skydoves/pokedex/compose/core/database/entitiy/mapper/PokemonInfoEntityMapper.kt
@@ -19,39 +19,26 @@
 import com.skydoves.pokedex.compose.core.database.entitiy.PokemonInfoEntity
 import com.skydoves.pokedex.compose.core.model.PokemonInfo
 
-object PokemonInfoEntityMapper : EntityMapper<PokemonInfo, PokemonInfoEntity> {
+fun PokemonInfo.asDatabaseEntity(): PokemonInfoEntity =
+    PokemonInfoEntity(
+        id = id,
+        name = name,
+        height = height,
+        weight = weight,
+        experience = experience,
+        types = types,
+        exp = exp,
+        stats = stats,
+    )
 
-    override fun asEntity(domain: PokemonInfo): PokemonInfoEntity {
-        return PokemonInfoEntity(
-            id = domain.id,
-            name = domain.name,
-            height = domain.height,
-            weight = domain.weight,
-            experience = domain.experience,
-            types = domain.types,
-            exp = domain.exp,
-            stats = domain.stats,
-        )
-    }
-
-    override fun asDomain(entity: PokemonInfoEntity): PokemonInfo {
-        return PokemonInfo(
-            id = entity.id,
-            name = entity.name,
-            height = entity.height,
-            weight = entity.weight,
-            experience = entity.experience,
-            types = entity.types,
-            exp = entity.exp,
-            stats = entity.stats,
-        )
-    }
-}
-
-fun PokemonInfo.asEntity(): PokemonInfoEntity {
-    return PokemonInfoEntityMapper.asEntity(this)
-}
-
-fun PokemonInfoEntity.asDomain(): PokemonInfo {
-    return PokemonInfoEntityMapper.asDomain(this)
-}
+fun PokemonInfoEntity.asPresentationModel(): PokemonInfo =
+    PokemonInfo(
+        id = id,
+        name = name,
+        height = height,
+        weight = weight,
+        experience = experience,
+        types = types,
+        exp = exp,
+        stats = stats,
+    )
diff --git a/app/src/main/kotlin/com/skydoves/pokedex/compose/core/designsystem/component/PokedexAppBar.kt b/app/src/main/kotlin/com/skydoves/pokedex/compose/core/designsystem/component/PokedexAppBar.kt
index 524c450..ede81e3 100644
--- a/app/src/main/kotlin/com/skydoves/pokedex/compose/core/designsystem/component/PokedexAppBar.kt
+++ b/app/src/main/kotlin/com/skydoves/pokedex/compose/core/designsystem/component/PokedexAppBar.kt
@@ -57,10 +57,7 @@
             )
         },
         colors =
-            TopAppBarDefaults.topAppBarColors()
-                .copy(
-                    containerColor = PokedexTheme.colors.primary,
-                ),
+            TopAppBarDefaults.topAppBarColors().copy(containerColor = PokedexTheme.colors.primary),
     )
 }
 
diff --git a/app/src/main/kotlin/com/skydoves/pokedex/compose/core/designsystem/component/PokedexProgressBar.kt b/app/src/main/kotlin/com/skydoves/pokedex/compose/core/designsystem/component/PokedexProgressBar.kt
index 716bf4b..5e44842 100644
--- a/app/src/main/kotlin/com/skydoves/pokedex/compose/core/designsystem/component/PokedexProgressBar.kt
+++ b/app/src/main/kotlin/com/skydoves/pokedex/compose/core/designsystem/component/PokedexProgressBar.kt
@@ -26,6 +26,7 @@
 import androidx.compose.foundation.layout.fillMaxWidth
 import androidx.compose.foundation.layout.height
 import androidx.compose.foundation.layout.padding
+import androidx.compose.foundation.layout.size
 import androidx.compose.foundation.layout.width
 import androidx.compose.foundation.shape.RoundedCornerShape
 import androidx.compose.material3.Text
@@ -43,12 +44,15 @@
 import androidx.compose.ui.layout.onSizeChanged
 import androidx.compose.ui.platform.LocalConfiguration
 import androidx.compose.ui.platform.LocalInspectionMode
+import androidx.compose.ui.platform.testTag
 import androidx.compose.ui.tooling.preview.Preview
 import androidx.compose.ui.unit.dp
 import androidx.compose.ui.unit.sp
 import com.skydoves.pokedex.compose.core.designsystem.theme.PokedexTheme
+import com.skydoves.pokedex.compose.core.designsystem.utils.TraceAsync
 import com.skydoves.pokedex.compose.core.designsystem.utils.pxToDp
 
+@Suppress("ConfigurationScreenWidthHeight")
 @Composable
 fun PokedexProgressBar(
     modifier: Modifier = Modifier,
@@ -64,7 +68,7 @@
                 screenWidth
             } else {
                 0f
-            },
+            }
         )
     }
 
@@ -78,37 +82,35 @@
                     color = PokedexTheme.colors.absoluteWhite,
                     shape = RoundedCornerShape(64.dp),
                 )
-                .clip(RoundedCornerShape(64.dp)),
+                .clip(RoundedCornerShape(64.dp))
     ) {
         var textWidth by remember { mutableIntStateOf(0) }
         val threshold = 16
         val isInner by
-            remember(
-                progressWidth,
-                textWidth,
-            ) {
+            remember(progressWidth, textWidth) {
                 mutableStateOf(progressWidth > (textWidth + threshold * 2))
             }
 
+        var animationRunning by remember { mutableStateOf(true) }
+        if (animationRunning) {
+            TraceAsync("PokedexProgressBar Animation (label = $label)")
+        }
+        Box(Modifier.size(1.dp).testTag("progress-animation-active-$animationRunning"))
         val animation: Float by
             animateFloatAsState(
                 targetValue = if (progressWidth == 0f) 0f else 1f,
                 // Configure the animation duration and easing.
                 animationSpec = tween(durationMillis = 950, easing = LinearOutSlowInEasing),
                 label = "",
+                finishedListener = { animationRunning = false },
             )
 
         Box(
             modifier =
                 Modifier.align(Alignment.CenterStart)
-                    .width(
-                        progressWidth.toInt().pxToDp() * animation,
-                    )
+                    .width(progressWidth.toInt().pxToDp() * animation)
                     .height(18.dp)
-                    .background(
-                        color = color,
-                        shape = RoundedCornerShape(64.dp),
-                    ),
+                    .background(color = color, shape = RoundedCornerShape(64.dp))
         ) {
             if (isInner) {
                 Text(
@@ -128,9 +130,7 @@
                 modifier =
                     Modifier.onSizeChanged { textWidth = it.width }
                         .align(Alignment.CenterStart)
-                        .padding(
-                            start = progressWidth.toInt().pxToDp() + threshold.pxToDp(),
-                        ),
+                        .padding(start = progressWidth.toInt().pxToDp() + threshold.pxToDp()),
                 text = label,
                 fontSize = 12.sp,
                 color = PokedexTheme.colors.absoluteBlack,
@@ -146,7 +146,7 @@
     PokedexTheme {
         Box(
             modifier =
-                Modifier.fillMaxWidth().height(120.dp).background(PokedexTheme.colors.background),
+                Modifier.fillMaxWidth().height(120.dp).background(PokedexTheme.colors.background)
         ) {
             PokedexProgressBar(
                 modifier = Modifier.align(Alignment.Center),
@@ -165,7 +165,7 @@
     PokedexTheme {
         Box(
             modifier =
-                Modifier.fillMaxWidth().height(120.dp).background(PokedexTheme.colors.background),
+                Modifier.fillMaxWidth().height(120.dp).background(PokedexTheme.colors.background)
         ) {
             PokedexProgressBar(
                 modifier = Modifier.fillMaxWidth().align(Alignment.Center),
diff --git a/app/src/main/kotlin/com/skydoves/pokedex/compose/core/designsystem/component/PokedexSharedElement.kt b/app/src/main/kotlin/com/skydoves/pokedex/compose/core/designsystem/component/PokedexSharedElement.kt
index e160416..5578c91 100644
--- a/app/src/main/kotlin/com/skydoves/pokedex/compose/core/designsystem/component/PokedexSharedElement.kt
+++ b/app/src/main/kotlin/com/skydoves/pokedex/compose/core/designsystem/component/PokedexSharedElement.kt
@@ -52,8 +52,8 @@
     state: SharedTransitionScope.SharedContentState,
     animatedVisibilityScope: AnimatedVisibilityScope,
     boundsTransform: BoundsTransform = DefaultBoundsTransform,
-    placeHolderSize: SharedTransitionScope.PlaceHolderSize =
-        SharedTransitionScope.PlaceHolderSize.contentSize,
+    placeholderSize: SharedTransitionScope.PlaceholderSize =
+        SharedTransitionScope.PlaceholderSize.ContentSize,
     renderInOverlayDuringTransition: Boolean = true,
     zIndexInOverlay: Float = 0f,
     clipInOverlayDuringTransition: SharedTransitionScope.OverlayClip = ParentClip,
@@ -66,7 +66,7 @@
                 sharedContentState = state,
                 animatedVisibilityScope = animatedVisibilityScope,
                 boundsTransform = boundsTransform,
-                placeHolderSize = placeHolderSize,
+                placeholderSize = placeholderSize,
                 renderInOverlayDuringTransition = renderInOverlayDuringTransition,
                 zIndexInOverlay = zIndexInOverlay,
                 clipInOverlayDuringTransition = clipInOverlayDuringTransition,
@@ -89,10 +89,7 @@
     }
 
 private val DefaultSpring =
-    spring(
-        stiffness = Spring.StiffnessMediumLow,
-        visibilityThreshold = Rect.VisibilityThreshold,
-    )
+    spring(stiffness = Spring.StiffnessMediumLow, visibilityThreshold = Rect.VisibilityThreshold)
 
 @OptIn(ExperimentalSharedTransitionApi::class)
 private val DefaultBoundsTransform = BoundsTransform { _, _ -> DefaultSpring }
diff --git a/app/src/main/kotlin/com/skydoves/pokedex/compose/core/designsystem/component/PokedexText.kt b/app/src/main/kotlin/com/skydoves/pokedex/compose/core/designsystem/component/PokedexText.kt
index b5cea7b..a35c45e 100644
--- a/app/src/main/kotlin/com/skydoves/pokedex/compose/core/designsystem/component/PokedexText.kt
+++ b/app/src/main/kotlin/com/skydoves/pokedex/compose/core/designsystem/component/PokedexText.kt
@@ -37,8 +37,8 @@
 @Composable
 fun PokedexText(
     text: String,
-    previewText: String = text,
     modifier: Modifier = Modifier,
+    previewText: String = text,
     color: Color = Color.Unspecified,
     fontSize: TextUnit = TextUnit.Unspecified,
     fontStyle: FontStyle? = null,
diff --git a/app/src/main/kotlin/com/skydoves/pokedex/compose/core/designsystem/theme/PokedexTheme.kt b/app/src/main/kotlin/com/skydoves/pokedex/compose/core/designsystem/theme/PokedexTheme.kt
index 7d9a743..d3d50ec 100644
--- a/app/src/main/kotlin/com/skydoves/pokedex/compose/core/designsystem/theme/PokedexTheme.kt
+++ b/app/src/main/kotlin/com/skydoves/pokedex/compose/core/designsystem/theme/PokedexTheme.kt
@@ -56,7 +56,7 @@
     ) {
         Box(
             modifier =
-                Modifier.background(background.color).semantics { testTagsAsResourceId = true },
+                Modifier.background(background.color).semantics { testTagsAsResourceId = true }
         ) {
             content()
         }
diff --git a/app/src/main/kotlin/com/skydoves/pokedex/compose/core/designsystem/utils/TraceAsync.kt b/app/src/main/kotlin/com/skydoves/pokedex/compose/core/designsystem/utils/TraceAsync.kt
new file mode 100644
index 0000000..35c057f
--- /dev/null
+++ b/app/src/main/kotlin/com/skydoves/pokedex/compose/core/designsystem/utils/TraceAsync.kt
@@ -0,0 +1,36 @@
+/*
+ * Copyright 2025 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.skydoves.pokedex.compose.core.designsystem.utils
+
+import androidx.compose.runtime.Composable
+import androidx.compose.runtime.DisposableEffect
+import androidx.tracing.Trace
+import kotlin.random.Random
+
+/**
+ * Trace the lifetime of this composable with an async trace block. Begins tracing when added to
+ * composition and ends tracing when removed. This is useful for tracking how long a composable is
+ * in a certain state, e.g. animations.
+ */
+@Composable
+fun TraceAsync(methodName: String) {
+    DisposableEffect(methodName) {
+        val tracingCookie = Random.nextInt()
+        Trace.beginAsyncSection(methodName, tracingCookie)
+        onDispose { Trace.endAsyncSection(methodName, tracingCookie) }
+    }
+}
diff --git a/app/src/main/kotlin/com/skydoves/pokedex/compose/core/di/RepositoryModule.kt b/app/src/main/kotlin/com/skydoves/pokedex/compose/core/di/RepositoryModule.kt
index b8790af..b0f7164 100644
--- a/app/src/main/kotlin/com/skydoves/pokedex/compose/core/di/RepositoryModule.kt
+++ b/app/src/main/kotlin/com/skydoves/pokedex/compose/core/di/RepositoryModule.kt
@@ -31,7 +31,7 @@
     private val pokemonDao: PokemonDao,
     private val pokemonInfoDao: PokemonInfoDao,
     private val ioDispatcher: CoroutineDispatcher,
-    private val apiUrl: HttpUrl
+    private val apiUrl: HttpUrl,
 ) {
     val detailsRepository: DetailsRepository by lazy {
         DetailsRepositoryImpl(pokedexClient, pokemonInfoDao, ioDispatcher)
diff --git a/app/src/main/kotlin/com/skydoves/pokedex/compose/core/model/PokemonInfo.kt b/app/src/main/kotlin/com/skydoves/pokedex/compose/core/model/PokemonInfo.kt
index 8909227..5c2f9c9 100644
--- a/app/src/main/kotlin/com/skydoves/pokedex/compose/core/model/PokemonInfo.kt
+++ b/app/src/main/kotlin/com/skydoves/pokedex/compose/core/model/PokemonInfo.kt
@@ -75,15 +75,9 @@
         @SerialName(value = "stat") val stat: Stat,
     )
 
-    @Serializable
-    data class Stat(
-        @SerialName(value = "name") val name: String,
-    )
+    @Serializable data class Stat(@SerialName(value = "name") val name: String)
 
-    @Serializable
-    data class Type(
-        @SerialName(value = "name") val name: String,
-    )
+    @Serializable data class Type(@SerialName(value = "name") val name: String)
 
     companion object {
         const val MAX_HP = 300
@@ -103,7 +97,7 @@
         weight = random.nextInt(80, 300),
         experience = random.nextInt(0, 100),
         types = listOf(FakePokemonTypeResponse(random)),
-        stats = listOf(fakePokemonStats(random))
+        stats = listOf(fakePokemonStats(random)),
     )
 }
 
@@ -122,7 +116,7 @@
     return PokemonInfo.StatsResponse(
         baseStat = random.nextInt(until = statMax),
         effort = random.nextInt(),
-        stat = stat
+        stat = stat,
     )
 }
 
@@ -133,7 +127,7 @@
         "A big one",
         "An adorable one",
         "A tiny one",
-        "A software-developing one"
+        "A software-developing one",
     )
 
 fun FakePokemonTypeResponse(random: Random = Random) =
diff --git a/app/src/main/kotlin/com/skydoves/pokedex/compose/core/model/PokemonNetworkModel.kt b/app/src/main/kotlin/com/skydoves/pokedex/compose/core/model/PokemonNetworkModel.kt
index 912f930..a568c58 100644
--- a/app/src/main/kotlin/com/skydoves/pokedex/compose/core/model/PokemonNetworkModel.kt
+++ b/app/src/main/kotlin/com/skydoves/pokedex/compose/core/model/PokemonNetworkModel.kt
@@ -17,33 +17,328 @@
 package com.skydoves.pokedex.compose.core.model
 
 import androidx.compose.runtime.Immutable
+import androidx.compose.ui.util.trace
 import kotlinx.serialization.Serializable
 
 @Immutable @Serializable class PokemonNetworkModel(val name: String)
 
-fun fakePokemonNetworkModels() = FakeRandomizedNames.map { name -> PokemonNetworkModel(name) }
+fun fakePokemonNetworkModels(pokemonNames: List<String>) =
+    pokemonNames.map { name -> PokemonNetworkModel(name) }
 
-val FakePokemonNames =
-    listOf(
-        "Jason",
-        "Jack",
-        "Anna",
-        "Bubir",
-        "Xanto",
-        "Vistesia",
-        "Ulint-y",
-        "Lapesareba",
-        "Nemo",
-        "Masurap"
-    )
-val FakeRandomizedNames by lazy {
-    FakePokemonNames.flatMap { name ->
-        val nameChars = name.toCharArray()
-        val shuffledChars = nameChars.apply { shuffle() }
+fun fakePokemonNames(limit: Int, offset: Int = 0): List<String> =
+    trace("fakePokemonNames(limit=$limit, offset=$offset)") {
+        return AllPokemonNames.subList(offset, minOf(offset + limit, AllPokemonNames.size))
+    }
+
+val AllPokemonNames =
+    trace("AllPokemonNames") {
         listOf(
-            shuffledChars.joinToString(""),
-            shuffledChars.apply { reverse() }.joinToString(""),
-            nameChars.apply { reverse() }.joinToString("")
+            "Ablazeon",
+            "Aerofer",
+            "Amphibyte",
+            "Anglark",
+            "Aquastrike",
+            "Arborix",
+            "Arctiflow",
+            "Armadile",
+            "Astrobat",
+            "Audioson",
+            "Backfire",
+            "Barricade",
+            "Basaltix",
+            "Batterfly",
+            "Beambyte",
+            "Berylix",
+            "Biobloom",
+            "Bizarroid",
+            "Blazefin",
+            "Boltclaw",
+            "Bonetail",
+            "Boulderisk",
+            "Bramblepuff",
+            "Bronzite",
+            "Bubblepod",
+            "Buggle",
+            "Bulbasa",
+            "Bumblefoot",
+            "Burrowix",
+            "Buzzardly",
+            "Cactuspear",
+            "Cadmiumite",
+            "Canopyawn",
+            "Capribble",
+            "Carapaceon",
+            "Caveworm",
+            "Celestrike",
+            "Cinderwing",
+            "Citrineon",
+            "Cliffhopper",
+            "Cloudillo",
+            "Coalabear",
+            "Cobaltix",
+            "Cometear",
+            "Coralite",
+            "Cosmite",
+            "Cragrawler",
+            "Creekle",
+            "Crestawl",
+            "Cryofrost",
+            "Cubblestone",
+            "Cyberspyke",
+            "Dampfly",
+            "Darkowl",
+            "Dawnwing",
+            "Deepshell",
+            "Deltawing",
+            "Desertail",
+            "Dewdrop",
+            "Diamondix",
+            "Digmound",
+            "Dirtdigger",
+            "Diskdrive",
+            "Dizzybat",
+            "Doombloom",
+            "Dracoat",
+            "Dragofin",
+            "Drakonix",
+            "Dreamist",
+            "Drillburrow",
+            "Dronix",
+            "Drowsipurr",
+            "Duneviper",
+            "Dustbunny",
+            "Dynamite",
+            "Ebonwing",
+            "Echoark",
+            "Eelixir",
+            "Emberfox",
+            "Emeraldite",
+            "Energlow",
+            "Equinox",
+            "Eruptile",
+            "Everbloom",
+            "Fableaf",
+            "Falconryx",
+            "Fangsnap",
+            "Featherfin",
+            "Ferroclaw",
+            "Fiercehorn",
+            "Firefang",
+            "Fissurion",
+            "Flashfire",
+            "Flatsnout",
+            "Flitterby",
+            "Flowertail",
+            "Flutterfin",
+            "Fogwhisper",
+            "Forestomp",
+            "Fossilite",
+            "Fractalix",
+            "Frostbite",
+            "Fungoyle",
+            "Galaxite",
+            "Galeonix",
+            "Gargoyle",
+            "Gascloud",
+            "Geminite",
+            "Glacieron",
+            "Glimmerbug",
+            "Gloomfang",
+            "Glowfin",
+            "Graniteel",
+            "Grassnake",
+            "Gravityx",
+            "Grubsqueak",
+            "Gunkpile",
+            "Gustwing",
+            "Hailstone",
+            "Hammerhead",
+            "Harmonic",
+            "Hazehorn",
+            "Heatwave",
+            "Heavyhorn",
+            "Helixite",
+            "Herbivore",
+            "Hexagon",
+            "Hillsnake",
+            "Hollowawk",
+            "Honeycomb",
+            "Hornetail",
+            "Hoverbug",
+            "Hummingwing",
+            "Hydrocoil",
+            "Icefang",
+            "Icicleon",
+            "Igniteon",
+            "Illumite",
+            "Ironhide",
+            "Jadeon",
+            "Jasperite",
+            "Jetstream",
+            "Jungleop",
+            "Juniperyn",
+            "Kelpfin",
+            "Kindlefly",
+            "Kingfisher",
+            "Knightowl",
+            "Knucklehead",
+            "Labyrinth",
+            "Lagooner",
+            "Lavashell",
+            "Leafwing",
+            "Leapingfrog",
+            "Lightbeam",
+            "Lightningbug",
+            "Limestone",
+            "Liquidite",
+            "Lunamoth",
+            "Magmite",
+            "Malachite",
+            "Mantisect",
+            "Marbleon",
+            "Marshwiggle",
+            "Maskito",
+            "Meadowfin",
+            "Megaton",
+            "Melodyte",
+            "Meteoric",
+            "Midnightowl",
+            "Mistralyn",
+            "Moltenix",
+            "Moonbeam",
+            "Mossback",
+            "Mudskipper",
+            "Mysticlaw",
+            "Nectarin",
+            "Netherfang",
+            "Nightshade",
+            "Nimbusowl",
+            "Nocturne",
+            "Novaflare",
+            "Nuggeteer",
+            "Obsidian",
+            "Oceanaut",
+            "Opalite",
+            "Orbitron",
+            "Overgrowth",
+            "Oxideon",
+            "Ozonefly",
+            "Palestone",
+            "Panthera",
+            "Parallax",
+            "Patchleaf",
+            "Pebblepuff",
+            "Pendulum",
+            "Peridot",
+            "Phantomist",
+            "Phasewalk",
+            "Pinecone",
+            "Pinwheel",
+            "Pixelite",
+            "Plainsrunner",
+            "Plasmafin",
+            "Plumbob",
+            "Poisonivy",
+            "Polaris",
+            "Pollenpuff",
+            "Pondskater",
+            "Prickleback",
+            "Prismite",
+            "Pumiceon",
+            "Pyrefly",
+            "Quakehorn",
+            "Quartzite",
+            "Quicksilver",
+            "Radiant",
+            "Raindrop",
+            "Raptoros",
+            "Razorfin",
+            "Reefwalker",
+            "Ripplefin",
+            "Riverunner",
+            "Rockhopper",
+            "Rubblebug",
+            "Rustmite",
+            "Saberfang",
+            "Saphireon",
+            "Scarabite",
+            "Scorchpaw",
+            "Seabreeze",
+            "Seaslug",
+            "Shadowclaw",
+            "Sharpfin",
+            "Shellshock",
+            "Shimmeron",
+            "Shockwave",
+            "Silicaon",
+            "Silverwing",
+            "Skitterbug",
+            "Skywhale",
+            "Slagpile",
+            "Sleetfoot",
+            "Smoketail",
+            "Snaggletooth",
+            "Snakeweed",
+            "Snowdrift",
+            "Solaris",
+            "Sonicboom",
+            "Sparkfly",
+            "Spectrite",
+            "Spikeball",
+            "Springtail",
+            "Stagbeetle",
+            "Starblaze",
+            "Stonefish",
+            "Stormcloud",
+            "Streamer",
+            "Strikewing",
+            "Sunbeam",
+            "Sunstone",
+            "Swampfin",
+            "Swiftail",
+            "Sycamore",
+            "Tanglefoot",
+            "Tarnish",
+            "Terraform",
+            "Thornback",
+            "Thunderbug",
+            "Tidalwave",
+            "Timberwolf",
+            "Tinytail",
+            "Topazite",
+            "Torrential",
+            "Toxiclaw",
+            "Tranquil",
+            "Tremorix",
+            "Tribyte",
+            "Tricorne",
+            "Twilight",
+            "Twisteron",
+            "Undergrowth",
+            "Undertow",
+            "Unicorn",
+            "Valiant",
+            "Vaporize",
+            "Venomite",
+            "Veridian",
+            "Vibraharp",
+            "Volcanic",
+            "Voltwing",
+            "Vortexon",
+            "Wallowby",
+            "Warpwing",
+            "Waterbug",
+            "Wavecrest",
+            "Waxwing",
+            "Wildfire",
+            "Windigo",
+            "Wispfire",
+            "Woodsprite",
+            "Wormhole",
+            "Wyvernix",
+            "Xenonix",
+            "Zephyron",
+            "Ziggurat",
+            "Zincite",
         )
     }
-}
diff --git a/app/src/main/kotlin/com/skydoves/pokedex/compose/core/navigation/LocalComposeNavigator.kt b/app/src/main/kotlin/com/skydoves/pokedex/compose/core/navigation/LocalComposeNavigator.kt
index f77ab07..fad499e 100644
--- a/app/src/main/kotlin/com/skydoves/pokedex/compose/core/navigation/LocalComposeNavigator.kt
+++ b/app/src/main/kotlin/com/skydoves/pokedex/compose/core/navigation/LocalComposeNavigator.kt
@@ -25,7 +25,7 @@
     compositionLocalOf {
         error(
             "No AppComposeNavigator provided! " +
-                "Make sure to wrap all usages of Pokedex components in PokedexTheme.",
+                "Make sure to wrap all usages of Pokedex components in PokedexTheme."
         )
     }
 
diff --git a/app/src/main/kotlin/com/skydoves/pokedex/compose/core/navigation/NavigationAnimation.kt b/app/src/main/kotlin/com/skydoves/pokedex/compose/core/navigation/NavigationAnimation.kt
index 4644c89..10063de 100644
--- a/app/src/main/kotlin/com/skydoves/pokedex/compose/core/navigation/NavigationAnimation.kt
+++ b/app/src/main/kotlin/com/skydoves/pokedex/compose/core/navigation/NavigationAnimation.kt
@@ -17,6 +17,11 @@
 package com.skydoves.pokedex.compose.core.navigation
 
 import androidx.compose.animation.core.tween
+import androidx.compose.animation.fadeIn
+import androidx.compose.animation.fadeOut
 import androidx.compose.ui.geometry.Rect
 
-val boundsTransform = { _: Rect, _: Rect -> tween<Rect>(550) }
+val navigationEnterTransition = fadeIn(tween(PokedexTransitionDurationMs))
+val navigationExitTransition = fadeOut(tween(PokedexTransitionDurationMs))
+val boundsTransform = { _: Rect, _: Rect -> tween<Rect>(PokedexTransitionDurationMs) }
+const val PokedexTransitionDurationMs = 700
diff --git a/app/src/main/kotlin/com/skydoves/pokedex/compose/core/navigation/NavigationCommand.kt b/app/src/main/kotlin/com/skydoves/pokedex/compose/core/navigation/NavigationCommand.kt
index fb3fc23..e6b7ed4 100644
--- a/app/src/main/kotlin/com/skydoves/pokedex/compose/core/navigation/NavigationCommand.kt
+++ b/app/src/main/kotlin/com/skydoves/pokedex/compose/core/navigation/NavigationCommand.kt
@@ -23,15 +23,15 @@
 }
 
 sealed interface ComposeNavigationCommand : NavigationCommand {
-    data class NavigateToRoute<T : Any>(val route: T, val options: NavOptions? = null) :
+    data class NavigateToRoute<T : PokedexScreen>(val route: T, val options: NavOptions? = null) :
         ComposeNavigationCommand
 
-    data class NavigateUpWithResult<R, T : Any>(
+    data class NavigateUpWithResult<R, T : PokedexScreen>(
         val key: String,
         val result: R,
         val route: T? = null,
     ) : ComposeNavigationCommand
 
-    data class PopUpToRoute<T : Any>(val route: T, val inclusive: Boolean) :
+    data class PopUpToRoute<T : PokedexScreen>(val route: T, val inclusive: Boolean) :
         ComposeNavigationCommand
 }
diff --git a/app/src/main/kotlin/com/skydoves/pokedex/compose/core/navigation/Navigator.kt b/app/src/main/kotlin/com/skydoves/pokedex/compose/core/navigation/Navigator.kt
index 1d54821..e94eb4e 100644
--- a/app/src/main/kotlin/com/skydoves/pokedex/compose/core/navigation/Navigator.kt
+++ b/app/src/main/kotlin/com/skydoves/pokedex/compose/core/navigation/Navigator.kt
@@ -37,7 +37,7 @@
     }
 }
 
-abstract class AppComposeNavigator<T : Any> : Navigator() {
+abstract class AppComposeNavigator<T : PokedexScreen> : Navigator() {
     abstract fun navigate(route: T, optionsBuilder: (NavOptionsBuilder.() -> Unit)? = null)
 
     abstract fun <R> navigateBackWithResult(key: String, result: R, route: T?)
@@ -56,14 +56,11 @@
     private fun NavController.handleComposeNavigationCommand(navigationCommand: NavigationCommand) {
         when (navigationCommand) {
             is ComposeNavigationCommand.NavigateToRoute<*> -> {
-                navigate(navigationCommand.route, navigationCommand.options)
+                navigate(navigationCommand.route.asRoute(), navigationCommand.options)
             }
             NavigationCommand.NavigateUp -> navigateUp()
             is ComposeNavigationCommand.PopUpToRoute<*> ->
-                popBackStack(
-                    navigationCommand.route,
-                    navigationCommand.inclusive,
-                )
+                popBackStack(navigationCommand.route.asRoute(), navigationCommand.inclusive)
             is ComposeNavigationCommand.NavigateUpWithResult<*, *> -> {
                 navUpWithResult(navigationCommand)
             }
@@ -71,17 +68,13 @@
     }
 
     private fun NavController.navUpWithResult(
-        navigationCommand: ComposeNavigationCommand.NavigateUpWithResult<*, *>,
+        navigationCommand: ComposeNavigationCommand.NavigateUpWithResult<*, *>
     ) {
         val backStackEntry =
-            navigationCommand.route?.let { getBackStackEntry(it) } ?: previousBackStackEntry
-        backStackEntry
-            ?.savedStateHandle
-            ?.set(
-                navigationCommand.key,
-                navigationCommand.result,
-            )
+            navigationCommand.route?.let { getBackStackEntry(it.asRoute()) }
+                ?: previousBackStackEntry
+        backStackEntry?.savedStateHandle?.set(navigationCommand.key, navigationCommand.result)
 
-        navigationCommand.route?.let { popBackStack(it, false) } ?: navigateUp()
+        navigationCommand.route?.let { popBackStack(it.asRoute(), false) } ?: navigateUp()
     }
 }
diff --git a/app/src/main/kotlin/com/skydoves/pokedex/compose/core/navigation/PokedexComposeNavigator.kt b/app/src/main/kotlin/com/skydoves/pokedex/compose/core/navigation/PokedexComposeNavigator.kt
index 2bd72c8..03dc29c 100644
--- a/app/src/main/kotlin/com/skydoves/pokedex/compose/core/navigation/PokedexComposeNavigator.kt
+++ b/app/src/main/kotlin/com/skydoves/pokedex/compose/core/navigation/PokedexComposeNavigator.kt
@@ -28,10 +28,7 @@
 
     override fun navigateAndClearBackStack(route: PokedexScreen) {
         navigationCommands.tryEmit(
-            ComposeNavigationCommand.NavigateToRoute(
-                route,
-                navOptions { popUpTo(0) },
-            ),
+            ComposeNavigationCommand.NavigateToRoute(route, navOptions { popUpTo(0) })
         )
     }
 
@@ -41,11 +38,7 @@
 
     override fun <R> navigateBackWithResult(key: String, result: R, route: PokedexScreen?) {
         navigationCommands.tryEmit(
-            ComposeNavigationCommand.NavigateUpWithResult(
-                key = key,
-                result = result,
-                route = route,
-            ),
+            ComposeNavigationCommand.NavigateUpWithResult(key = key, result = result, route = route)
         )
     }
 }
diff --git a/app/src/main/kotlin/com/skydoves/pokedex/compose/core/navigation/PokedexScreen.kt b/app/src/main/kotlin/com/skydoves/pokedex/compose/core/navigation/PokedexScreen.kt
index e99c3a3..eb59644 100644
--- a/app/src/main/kotlin/com/skydoves/pokedex/compose/core/navigation/PokedexScreen.kt
+++ b/app/src/main/kotlin/com/skydoves/pokedex/compose/core/navigation/PokedexScreen.kt
@@ -37,11 +37,24 @@
 import kotlinx.serialization.Serializable
 
 sealed interface PokedexScreen {
-    @Serializable object Home : PokedexScreen
+    fun asRoute(): String
+
+    @Serializable
+    object Home : PokedexScreen {
+        const val NAVIGATION_ROUTE = "home"
+
+        override fun asRoute() = NAVIGATION_ROUTE
+    }
 
     @Serializable
     data class Details(val pokemon: Pokemon) : PokedexScreen {
+        override fun asRoute() = createRoute(pokemon.name)
+
         companion object {
+            const val NAVIGATION_ROUTE = "pokemon/{name}/"
+
+            fun createRoute(name: String) = "pokemon/$name/"
+
             val typeMap = mapOf(typeOf<Pokemon>() to PokemonType)
         }
     }
diff --git a/app/src/main/kotlin/com/skydoves/pokedex/compose/core/network/PokedexAppDispatchers.kt b/app/src/main/kotlin/com/skydoves/pokedex/compose/core/network/PokedexAppDispatchers.kt
index 52021b9..e272254 100644
--- a/app/src/main/kotlin/com/skydoves/pokedex/compose/core/network/PokedexAppDispatchers.kt
+++ b/app/src/main/kotlin/com/skydoves/pokedex/compose/core/network/PokedexAppDispatchers.kt
@@ -22,5 +22,5 @@
 annotation class Dispatcher(@Suppress("Unused") val pokedexAppDispatchers: PokedexAppDispatchers)
 
 enum class PokedexAppDispatchers {
-    IO,
+    IO
 }
diff --git a/app/src/main/kotlin/com/skydoves/pokedex/compose/core/network/di/ModuleLocator.kt b/app/src/main/kotlin/com/skydoves/pokedex/compose/core/network/di/ModuleLocator.kt
index bdd7e89..3aab54e 100644
--- a/app/src/main/kotlin/com/skydoves/pokedex/compose/core/network/di/ModuleLocator.kt
+++ b/app/src/main/kotlin/com/skydoves/pokedex/compose/core/network/di/ModuleLocator.kt
@@ -37,13 +37,13 @@
     val networkModule by lazy {
         NetworkModule(
             json = serializationModule.json,
-            networkCoroutineContext = dispatchersModule.io
+            networkCoroutineContext = dispatchersModule.io,
         )
     }
     val databaseModule by lazy {
         DatabaseModule(
             context = requireNotNull(context) { "Please attach the context using attach" },
-            json = serializationModule.json
+            json = serializationModule.json,
         )
     }
     val repositoryModule by lazy {
@@ -52,7 +52,7 @@
             databaseModule.pokemonDao,
             databaseModule.pokemonInfoDao,
             dispatchersModule.io,
-            networkModule.baseUrl
+            networkModule.baseUrl,
         )
     }
 }
diff --git a/app/src/main/kotlin/com/skydoves/pokedex/compose/core/network/di/NetworkModule.kt b/app/src/main/kotlin/com/skydoves/pokedex/compose/core/network/di/NetworkModule.kt
index 0172c24..51250b6 100644
--- a/app/src/main/kotlin/com/skydoves/pokedex/compose/core/network/di/NetworkModule.kt
+++ b/app/src/main/kotlin/com/skydoves/pokedex/compose/core/network/di/NetworkModule.kt
@@ -82,16 +82,12 @@
 
     fun okHttpClientFactory(): OkHttpClient {
         return OkHttpClient.Builder()
-            .apply {
-                if (true) {
-                    this.addNetworkInterceptor(
-                        HttpLoggingInterceptor().apply { level = HttpLoggingInterceptor.Level.BODY }
-                    )
-                }
-            }
+            .addNetworkInterceptor(
+                HttpLoggingInterceptor().apply { level = HttpLoggingInterceptor.Level.BODY }
+            )
             .sslSocketFactory(
                 sslSocketFactory = localhostCertificates.sslSocketFactory(),
-                trustManager = localhostCertificates.trustManager
+                trustManager = localhostCertificates.trustManager,
             )
             .build()
     }
diff --git a/app/src/main/kotlin/com/skydoves/pokedex/compose/core/network/model/PokemonResponse.kt b/app/src/main/kotlin/com/skydoves/pokedex/compose/core/network/model/PokemonResponse.kt
index d87b7b7..e7f4798 100644
--- a/app/src/main/kotlin/com/skydoves/pokedex/compose/core/network/model/PokemonResponse.kt
+++ b/app/src/main/kotlin/com/skydoves/pokedex/compose/core/network/model/PokemonResponse.kt
@@ -16,6 +16,7 @@
 
 package com.skydoves.pokedex.compose.core.network.model
 
+import com.skydoves.pokedex.compose.core.model.AllPokemonNames
 import com.skydoves.pokedex.compose.core.model.PokemonNetworkModel
 import com.skydoves.pokedex.compose.core.model.fakePokemonNetworkModels
 import kotlinx.serialization.SerialName
@@ -35,5 +36,6 @@
  * @param pokemons The pokemons to be contained in the response, a list of generated items with fake
  *   data by default.
  */
-fun fakePokemonResponse(pokemons: List<PokemonNetworkModel> = fakePokemonNetworkModels()) =
-    PokemonResponse(count = pokemons.size, previous = null, next = null, results = pokemons)
+fun fakePokemonResponse(
+    pokemons: List<PokemonNetworkModel> = fakePokemonNetworkModels(AllPokemonNames)
+) = PokemonResponse(count = pokemons.size, previous = null, next = null, results = pokemons)
diff --git a/app/src/main/kotlin/com/skydoves/pokedex/compose/core/network/service/PokedexClient.kt b/app/src/main/kotlin/com/skydoves/pokedex/compose/core/network/service/PokedexClient.kt
index 51c24a3..dbac51e 100644
--- a/app/src/main/kotlin/com/skydoves/pokedex/compose/core/network/service/PokedexClient.kt
+++ b/app/src/main/kotlin/com/skydoves/pokedex/compose/core/network/service/PokedexClient.kt
@@ -23,10 +23,7 @@
 
     suspend fun fetchPokemonList(page: Int): Result<PokemonResponse> =
         kotlin.runCatching {
-            pokedexService.fetchPokemonList(
-                limit = PAGING_SIZE,
-                offset = page * PAGING_SIZE,
-            )
+            pokedexService.fetchPokemonList(limit = PAGING_SIZE, offset = page * PAGING_SIZE)
         }
 
     suspend fun fetchPokemonInfo(name: String): Result<PokemonInfo> =
diff --git a/app/src/main/kotlin/com/skydoves/pokedex/compose/core/network/service/PokedexMockWebServer.kt b/app/src/main/kotlin/com/skydoves/pokedex/compose/core/network/service/PokedexMockWebServer.kt
index c3f8c1d..8ed2629 100644
--- a/app/src/main/kotlin/com/skydoves/pokedex/compose/core/network/service/PokedexMockWebServer.kt
+++ b/app/src/main/kotlin/com/skydoves/pokedex/compose/core/network/service/PokedexMockWebServer.kt
@@ -18,8 +18,10 @@
 
 import android.graphics.Bitmap
 import androidx.compose.integration.hero.common.implementation.GradientBitmap
-import com.skydoves.pokedex.compose.core.model.FakeRandomizedNames
+import com.skydoves.pokedex.compose.core.model.AllPokemonNames
 import com.skydoves.pokedex.compose.core.model.fakePokemonInfo
+import com.skydoves.pokedex.compose.core.model.fakePokemonNames
+import com.skydoves.pokedex.compose.core.model.fakePokemonNetworkModels
 import com.skydoves.pokedex.compose.core.network.model.fakePokemonResponse
 import kotlinx.serialization.encodeToString
 import kotlinx.serialization.json.Json
@@ -48,7 +50,7 @@
         val response =
             try {
                 when {
-                    pokemonEndpointRegex.matches(requestPath) -> pokemonHandler()
+                    pokemonEndpointRegex.matches(requestPath) -> pokemonHandler(request)
                     pokemonInfoEndpointRegex.matches(requestPath) -> pokemonInfoHandler(request)
                     pokemonImageEndpointRegex.matches(requestPath) -> pokemonImageHandler(request)
                     else -> MockResponse().setResponseCode(404)
@@ -62,10 +64,19 @@
         return response
     }
 
-    private fun pokemonHandler(): MockResponse {
-        return MockResponse()
-            .setResponseCode(200)
-            .setBody(json.encodeToString(fakePokemonResponse()))
+    private fun pokemonHandler(request: RecordedRequest): MockResponse {
+        val requestUrl = request.requestUrl
+        if (requestUrl == null) return MockResponse().setResponseCode(404)
+        val maxPokemon = requestUrl.queryParameter("limit")?.toInt() ?: 20
+        val fetchingOffset = requestUrl.queryParameter("offset")?.toInt() ?: 0
+        val response =
+            fakePokemonResponse(
+                pokemons =
+                    fakePokemonNetworkModels(
+                        pokemonNames = fakePokemonNames(limit = maxPokemon, offset = fetchingOffset)
+                    )
+            )
+        return MockResponse().setResponseCode(200).setBody(json.encodeToString(response))
     }
 
     private fun pokemonInfoHandler(request: RecordedRequest): MockResponse {
@@ -74,13 +85,15 @@
         val pokemonName = requestUrl.pathSegments.last()
         val fakePokemonInfo =
             json.encodeToString(
-                fakePokemonInfo(id = FakeRandomizedNames.indexOf(pokemonName), name = pokemonName)
+                fakePokemonInfo(id = AllPokemonNames.indexOf(pokemonName), name = pokemonName)
             )
         return MockResponse().setResponseCode(200).setBody(fakePokemonInfo)
     }
 
     private fun pokemonImageHandler(request: RecordedRequest): MockResponse {
-        val pathSegments = request.requestUrl!!.pathSegments
+        val requestUrl = request.requestUrl
+        if (requestUrl == null) return MockResponse().setResponseCode(404)
+        val pathSegments = requestUrl.pathSegments
         val pokemonName = pathSegments[pathSegments.size - 2]
         val image = GradientBitmap(width = 500, height = 500, seed = pokemonName.hashCode())
         val buffer = Buffer()
diff --git a/app/src/main/kotlin/com/skydoves/pokedex/compose/core/preview/PokedexPreviewTheme.kt b/app/src/main/kotlin/com/skydoves/pokedex/compose/core/preview/PokedexPreviewTheme.kt
index 01c84aa..2a00e29 100644
--- a/app/src/main/kotlin/com/skydoves/pokedex/compose/core/preview/PokedexPreviewTheme.kt
+++ b/app/src/main/kotlin/com/skydoves/pokedex/compose/core/preview/PokedexPreviewTheme.kt
@@ -29,11 +29,9 @@
 @OptIn(ExperimentalSharedTransitionApi::class)
 @Composable
 fun PokedexPreviewTheme(
-    content: @Composable SharedTransitionScope.(AnimatedVisibilityScope) -> Unit,
+    content: @Composable SharedTransitionScope.(AnimatedVisibilityScope) -> Unit
 ) {
-    CompositionLocalProvider(
-        LocalComposeNavigator provides PokedexComposeNavigator(),
-    ) {
+    CompositionLocalProvider(LocalComposeNavigator provides PokedexComposeNavigator()) {
         PokedexTheme {
             SharedTransitionScope { modifier ->
                 AnimatedVisibility(modifier = modifier, visible = true, label = "") {
diff --git a/app/src/main/kotlin/com/skydoves/pokedex/compose/core/preview/PreviewUtils.kt b/app/src/main/kotlin/com/skydoves/pokedex/compose/core/preview/PreviewUtils.kt
index 058a1e4..8da64cc 100644
--- a/app/src/main/kotlin/com/skydoves/pokedex/compose/core/preview/PreviewUtils.kt
+++ b/app/src/main/kotlin/com/skydoves/pokedex/compose/core/preview/PreviewUtils.kt
@@ -23,18 +23,14 @@
 
     @Suppress("Unused")
     fun mockPokemon() =
-        Pokemon(
-            page = 0,
-            name = "bulbasaur",
-            imageUrl = "https://pokeapi.co/api/v2/pokemon/1/",
-        )
+        Pokemon(page = 0, name = "bulbasaur", imageUrl = "https://pokeapi.co/api/v2/pokemon/1/")
 
     fun mockPokemonList() =
         List(10) {
             Pokemon(
                 page = 0,
                 name = "bulbasaur$it",
-                imageUrl = "https://pokeapi.co/api/v2/pokemon/1/"
+                imageUrl = "https://pokeapi.co/api/v2/pokemon/1/",
             )
         }
 
@@ -55,22 +51,22 @@
                     PokemonInfo.StatsResponse(
                         baseStat = 20,
                         effort = 0,
-                        stat = PokemonInfo.Stat("hp")
+                        stat = PokemonInfo.Stat("hp"),
                     ),
                     PokemonInfo.StatsResponse(
                         baseStat = 40,
                         effort = 0,
-                        stat = PokemonInfo.Stat("attack")
+                        stat = PokemonInfo.Stat("attack"),
                     ),
                     PokemonInfo.StatsResponse(
                         baseStat = 60,
                         effort = 0,
-                        stat = PokemonInfo.Stat("defense")
+                        stat = PokemonInfo.Stat("defense"),
                     ),
                     PokemonInfo.StatsResponse(
                         baseStat = 80,
                         effort = 0,
-                        stat = PokemonInfo.Stat("attack")
+                        stat = PokemonInfo.Stat("attack"),
                     ),
                 ),
         )
diff --git a/app/src/main/kotlin/com/skydoves/pokedex/compose/core/viewmodel/PokedexViewModelFactory.kt b/app/src/main/kotlin/com/skydoves/pokedex/compose/core/viewmodel/PokedexViewModelFactory.kt
index 9765c77..8573547 100644
--- a/app/src/main/kotlin/com/skydoves/pokedex/compose/core/viewmodel/PokedexViewModelFactory.kt
+++ b/app/src/main/kotlin/com/skydoves/pokedex/compose/core/viewmodel/PokedexViewModelFactory.kt
@@ -16,22 +16,14 @@
 
 package com.skydoves.pokedex.compose.core.viewmodel
 
-import androidx.compose.runtime.compositionLocalWithComputedDefaultOf
-import androidx.compose.ui.platform.LocalContext
 import androidx.lifecycle.createSavedStateHandle
 import androidx.lifecycle.viewmodel.initializer
 import androidx.lifecycle.viewmodel.viewModelFactory
 import com.skydoves.pokedex.compose.core.di.RepositoryModule
-import com.skydoves.pokedex.compose.core.network.di.ModuleLocator
 import com.skydoves.pokedex.compose.feature.details.DetailsViewModel
 import com.skydoves.pokedex.compose.feature.home.HomeViewModel
 
-val LocalPokedexViewModelFactory = compositionLocalWithComputedDefaultOf {
-    ModuleLocator.attach(context = { LocalContext.currentValue })
-    PokedexViewModelFactory(ModuleLocator.repositoryModule)
-}
-
-fun PokedexViewModelFactory(repositoryModule: RepositoryModule) = viewModelFactory {
+fun pokedexViewModelFactory(repositoryModule: RepositoryModule) = viewModelFactory {
     initializer { DetailsViewModel(repositoryModule.detailsRepository, createSavedStateHandle()) }
     initializer { HomeViewModel(repositoryModule.homeRepository) }
 }
diff --git a/app/src/main/kotlin/com/skydoves/pokedex/compose/core/viewmodel/ViewModelStateFlow.kt b/app/src/main/kotlin/com/skydoves/pokedex/compose/core/viewmodel/ViewModelStateFlow.kt
index 0ba4a27..ff7c0a0 100644
--- a/app/src/main/kotlin/com/skydoves/pokedex/compose/core/viewmodel/ViewModelStateFlow.kt
+++ b/app/src/main/kotlin/com/skydoves/pokedex/compose/core/viewmodel/ViewModelStateFlow.kt
@@ -17,6 +17,7 @@
 package com.skydoves.pokedex.compose.core.viewmodel
 
 import kotlinx.coroutines.ExperimentalCoroutinesApi
+import kotlinx.coroutines.ExperimentalForInheritanceCoroutinesApi
 import kotlinx.coroutines.flow.FlowCollector
 import kotlinx.coroutines.flow.MutableStateFlow
 import kotlinx.coroutines.flow.StateFlow
@@ -30,12 +31,12 @@
  * once Kotlin 2.0 stable version is released and the new Compose compiler is compatible with Kotlin
  * 2.0.
  */
+// TODO: Remove this class b/400932000
+@OptIn(ExperimentalForInheritanceCoroutinesApi::class)
 class ViewModelStateFlow<T>(private val key: ViewModelKey, value: T) : MutableStateFlow<T> {
 
     private val mutableStateFlow: MutableStateFlow<Map<ViewModelKey, T>> =
-        MutableStateFlow(
-            mapOf(key to value),
-        )
+        MutableStateFlow(mapOf(key to value))
 
     override val subscriptionCount: StateFlow<Int>
         get() = mutableStateFlow.subscriptionCount
@@ -45,7 +46,7 @@
         if (key != this.key) {
             throw IllegalArgumentException(
                 "Used different key to emit new value: $value!" +
-                    "Don't manipulate key value or try to emit out of ViewModels",
+                    "Don't manipulate key value or try to emit out of ViewModels"
             )
         }
 
@@ -60,7 +61,7 @@
         if (key != this.key) {
             throw IllegalArgumentException(
                 "Used different key to emit new value: $value!" +
-                    "Don't manipulate key value or try to emit out of ViewModels",
+                    "Don't manipulate key value or try to emit out of ViewModels"
             )
         }
 
diff --git a/app/src/main/kotlin/com/skydoves/pokedex/compose/feature/details/DetailsBackground.kt b/app/src/main/kotlin/com/skydoves/pokedex/compose/feature/details/DetailsBackground.kt
index e5142eb..b7758ff 100644
--- a/app/src/main/kotlin/com/skydoves/pokedex/compose/feature/details/DetailsBackground.kt
+++ b/app/src/main/kotlin/com/skydoves/pokedex/compose/feature/details/DetailsBackground.kt
@@ -36,11 +36,7 @@
                 val domainColor = Color(domain)
                 if (light != null) {
                     val lightColor = Color(light)
-                    val gradient =
-                        arrayOf(
-                            0.0f to domainColor,
-                            1f to lightColor,
-                        )
+                    val gradient = arrayOf(0.0f to domainColor, 1f to lightColor)
                     Brush.verticalGradient(colorStops = gradient)
                 } else {
                     Brush.linearGradient(colors = listOf(domainColor, domainColor))
diff --git a/app/src/main/kotlin/com/skydoves/pokedex/compose/feature/details/DetailsViewModel.kt b/app/src/main/kotlin/com/skydoves/pokedex/compose/feature/details/DetailsViewModel.kt
index 2b0fd6b..1f985e4 100644
--- a/app/src/main/kotlin/com/skydoves/pokedex/compose/feature/details/DetailsViewModel.kt
+++ b/app/src/main/kotlin/com/skydoves/pokedex/compose/feature/details/DetailsViewModel.kt
@@ -20,7 +20,6 @@
 import androidx.lifecycle.SavedStateHandle
 import androidx.lifecycle.viewModelScope
 import com.skydoves.pokedex.compose.core.data.repository.details.DetailsRepository
-import com.skydoves.pokedex.compose.core.model.Pokemon
 import com.skydoves.pokedex.compose.core.model.PokemonInfo
 import com.skydoves.pokedex.compose.core.viewmodel.BaseViewModel
 import com.skydoves.pokedex.compose.core.viewmodel.ViewModelStateFlow
@@ -31,22 +30,20 @@
 import kotlinx.coroutines.flow.flatMapLatest
 import kotlinx.coroutines.flow.stateIn
 
-class DetailsViewModel(
-    detailsRepository: DetailsRepository,
-    savedStateHandle: SavedStateHandle,
-) : BaseViewModel() {
+class DetailsViewModel(detailsRepository: DetailsRepository, savedStateHandle: SavedStateHandle) :
+    BaseViewModel() {
 
     internal val uiState: ViewModelStateFlow<DetailsUiState> =
         viewModelStateFlow(DetailsUiState.Loading)
 
-    val pokemon = savedStateHandle.getStateFlow<Pokemon?>("pokemon", null)
+    val pokemonName = savedStateHandle.getStateFlow<String?>("name", null)
     @OptIn(ExperimentalCoroutinesApi::class)
     val pokemonInfo: StateFlow<PokemonInfo?> =
-        pokemon
+        pokemonName
             .filterNotNull()
             .flatMapLatest { pokemon ->
                 detailsRepository.fetchPokemonInfo(
-                    name = pokemon.name.replaceFirstChar { it.lowercase() },
+                    name = pokemon.replaceFirstChar { it.lowercase() },
                     onComplete = { uiState.tryEmit(key, DetailsUiState.Idle) },
                     onError = { uiState.tryEmit(key, DetailsUiState.Error(it)) },
                 )
diff --git a/app/src/main/kotlin/com/skydoves/pokedex/compose/feature/details/PokedexDetails.kt b/app/src/main/kotlin/com/skydoves/pokedex/compose/feature/details/PokedexDetails.kt
index ef9182b..999d5de 100644
--- a/app/src/main/kotlin/com/skydoves/pokedex/compose/feature/details/PokedexDetails.kt
+++ b/app/src/main/kotlin/com/skydoves/pokedex/compose/feature/details/PokedexDetails.kt
@@ -22,6 +22,7 @@
 import androidx.compose.animation.AnimatedVisibilityScope
 import androidx.compose.animation.ExperimentalSharedTransitionApi
 import androidx.compose.animation.SharedTransitionScope
+import androidx.compose.animation.core.tween
 import androidx.compose.foundation.background
 import androidx.compose.foundation.clickable
 import androidx.compose.foundation.layout.Arrangement
@@ -47,10 +48,13 @@
 import androidx.compose.ui.Modifier
 import androidx.compose.ui.draw.shadow
 import androidx.compose.ui.layout.ContentScale
+import androidx.compose.ui.platform.LocalContext
 import androidx.compose.ui.platform.LocalInspectionMode
 import androidx.compose.ui.platform.testTag
 import androidx.compose.ui.res.painterResource
 import androidx.compose.ui.res.stringResource
+import androidx.compose.ui.semantics.semantics
+import androidx.compose.ui.semantics.testTag
 import androidx.compose.ui.text.font.FontWeight
 import androidx.compose.ui.text.style.TextAlign
 import androidx.compose.ui.tooling.preview.Preview
@@ -60,45 +64,53 @@
 import androidx.lifecycle.compose.collectAsStateWithLifecycle
 import androidx.lifecycle.viewmodel.compose.viewModel
 import androidx.palette.graphics.Palette
+import coil3.compose.AsyncImage
+import coil3.request.ImageRequest
+import coil3.request.crossfade
 import com.bumptech.glide.integration.compose.CrossFade
 import com.bumptech.glide.integration.compose.ExperimentalGlideComposeApi
 import com.bumptech.glide.integration.compose.GlideImage
 import com.bumptech.glide.integration.compose.placeholder
 import com.skydoves.pokedex.compose.R
+import com.skydoves.pokedex.compose.core.PokedexFeatureFlags
 import com.skydoves.pokedex.compose.core.data.repository.details.FakeDetailsRepository
+import com.skydoves.pokedex.compose.core.database.entitiy.mapper.getPokemonImageUrlByName
 import com.skydoves.pokedex.compose.core.designsystem.component.PokedexCircularProgress
 import com.skydoves.pokedex.compose.core.designsystem.component.PokedexText
 import com.skydoves.pokedex.compose.core.designsystem.component.pokedexSharedElement
 import com.skydoves.pokedex.compose.core.designsystem.theme.PokedexTheme
 import com.skydoves.pokedex.compose.core.designsystem.utils.getPokemonTypeColor
-import com.skydoves.pokedex.compose.core.model.Pokemon
 import com.skydoves.pokedex.compose.core.model.PokemonInfo
 import com.skydoves.pokedex.compose.core.navigation.boundsTransform
 import com.skydoves.pokedex.compose.core.navigation.currentComposeNavigator
 import com.skydoves.pokedex.compose.core.preview.PokedexPreviewTheme
 import com.skydoves.pokedex.compose.core.preview.PreviewUtils
-import com.skydoves.pokedex.compose.core.viewmodel.LocalPokedexViewModelFactory
 
 @Composable
 fun PokedexDetails(
-    sharedTransitionScope: SharedTransitionScope,
+    sharedTransitionScope: SharedTransitionScope?,
     animatedVisibilityScope: AnimatedVisibilityScope,
-    detailsViewModel: DetailsViewModel = viewModel(factory = LocalPokedexViewModelFactory.current),
+    detailsViewModel: DetailsViewModel,
 ) {
     val uiState by detailsViewModel.uiState.collectAsStateWithLifecycle()
-    val pokemon by detailsViewModel.pokemon.collectAsStateWithLifecycle()
+    val pokemonName by detailsViewModel.pokemonName.collectAsStateWithLifecycle()
     val pokemonInfo by detailsViewModel.pokemonInfo.collectAsStateWithLifecycle()
 
     Column(
         modifier =
-            Modifier.fillMaxSize().verticalScroll(rememberScrollState()).testTag("PokedexDetails"),
+            Modifier.fillMaxSize().verticalScroll(rememberScrollState()).testTag("PokedexDetails")
     ) {
         DetailsHeader(
             sharedTransitionScope = sharedTransitionScope,
             animatedVisibilityScope = animatedVisibilityScope,
-            pokemon = pokemon,
+            pokemonName = pokemonName,
             pokemonInfo = pokemonInfo,
         )
+        if (sharedTransitionScope != null) {
+            val statusText =
+                "pokedex-details-transition-active-${sharedTransitionScope.isTransitionActive}"
+            Text(statusText, Modifier.semantics { testTag = statusText })
+        }
 
         if (uiState == DetailsUiState.Idle && pokemonInfo != null) {
             DetailsInfo(pokemonInfo = pokemonInfo!!)
@@ -110,23 +122,17 @@
     }
 }
 
-@OptIn(ExperimentalGlideComposeApi::class)
 @Composable
 private fun DetailsHeader(
-    sharedTransitionScope: SharedTransitionScope,
+    sharedTransitionScope: SharedTransitionScope?,
     animatedVisibilityScope: AnimatedVisibilityScope,
-    pokemon: Pokemon?,
+    pokemonName: String?,
     pokemonInfo: PokemonInfo?,
 ) {
     val composeNavigator = currentComposeNavigator
     val palette by remember { mutableStateOf<Palette?>(null) }
     val shape =
-        RoundedCornerShape(
-            topStart = 0.dp,
-            topEnd = 0.dp,
-            bottomStart = 64.dp,
-            bottomEnd = 64.dp,
-        )
+        RoundedCornerShape(topStart = 0.dp, topEnd = 0.dp, bottomStart = 64.dp, bottomEnd = 64.dp)
 
     val backgroundBrush by palette.paletteBackgroundBrush()
 
@@ -135,14 +141,17 @@
             Modifier.fillMaxWidth()
                 .height(290.dp)
                 .shadow(elevation = 9.dp, shape = shape)
-                .background(brush = backgroundBrush, shape = shape),
+                .background(brush = backgroundBrush, shape = shape)
     ) {
         Row(
             modifier = Modifier.padding(12.dp).statusBarsPadding(),
             verticalAlignment = Alignment.CenterVertically,
         ) {
             Icon(
-                modifier = Modifier.padding(end = 6.dp).clickable { composeNavigator.navigateUp() },
+                modifier =
+                    Modifier.testTag("pokedexDetailsBack").padding(end = 6.dp).clickable {
+                        composeNavigator.navigateUp()
+                    },
                 painter = painterResource(id = R.drawable.ic_arrow),
                 tint = PokedexTheme.colors.absoluteWhite,
                 contentDescription = null,
@@ -150,7 +159,7 @@
 
             Text(
                 modifier = Modifier.padding(horizontal = 10.dp),
-                text = pokemon?.name.orEmpty(),
+                text = pokemonName.orEmpty(),
                 color = PokedexTheme.colors.absoluteWhite,
                 fontWeight = FontWeight.Bold,
                 fontSize = 18.sp,
@@ -166,26 +175,29 @@
             fontSize = 18.sp,
         )
 
-        GlideImage(
+        PokemonHeaderImage(
+            pokemonName,
             modifier =
                 Modifier.align(Alignment.BottomCenter)
                     .padding(bottom = 20.dp)
                     .size(190.dp)
-                    .pokedexSharedElement(
-                        sharedTransitionScope = sharedTransitionScope,
-                        isLocalInspectionMode = LocalInspectionMode.current,
-                        state =
-                            sharedTransitionScope.rememberSharedContentState(
-                                key = "image-${pokemon?.name}"
-                            ),
-                        animatedVisibilityScope = animatedVisibilityScope,
-                        boundsTransform = boundsTransform,
+                    .then(
+                        if (
+                            sharedTransitionScope != null &&
+                                PokedexFeatureFlags.EnableSharedElementTransitions
+                        ) {
+                            Modifier.pokedexSharedElement(
+                                sharedTransitionScope = sharedTransitionScope,
+                                isLocalInspectionMode = LocalInspectionMode.current,
+                                state =
+                                    sharedTransitionScope.rememberSharedContentState(
+                                        key = "image-$pokemonName"
+                                    ),
+                                animatedVisibilityScope = animatedVisibilityScope,
+                                boundsTransform = boundsTransform,
+                            )
+                        } else Modifier
                     ),
-            model = pokemon?.imageUrl,
-            contentScale = ContentScale.Inside,
-            transition = CrossFade,
-            contentDescription = pokemon?.name,
-            loading = placeholder(painterResource(id = R.drawable.pokemon_preview)),
         )
     }
 
@@ -193,17 +205,24 @@
         modifier =
             Modifier.padding(top = 24.dp)
                 .fillMaxWidth()
-                .pokedexSharedElement(
-                    sharedTransitionScope = sharedTransitionScope,
-                    isLocalInspectionMode = LocalInspectionMode.current,
-                    state =
-                        sharedTransitionScope.rememberSharedContentState(
-                            key = "name-${pokemon?.name}"
-                        ),
-                    animatedVisibilityScope = animatedVisibilityScope,
-                    boundsTransform = boundsTransform,
+                .then(
+                    if (
+                        sharedTransitionScope != null &&
+                            PokedexFeatureFlags.EnableSharedElementTransitions
+                    ) {
+                        Modifier.pokedexSharedElement(
+                            sharedTransitionScope = sharedTransitionScope,
+                            isLocalInspectionMode = LocalInspectionMode.current,
+                            state =
+                                sharedTransitionScope.rememberSharedContentState(
+                                    key = "name-$pokemonName"
+                                ),
+                            animatedVisibilityScope = animatedVisibilityScope,
+                            boundsTransform = boundsTransform,
+                        )
+                    } else Modifier
                 ),
-        text = pokemon?.name.orEmpty(),
+        text = pokemonName.orEmpty(),
         previewText = "skydoves",
         color = PokedexTheme.colors.black,
         fontWeight = FontWeight.Bold,
@@ -212,6 +231,37 @@
     )
 }
 
+@OptIn(ExperimentalGlideComposeApi::class)
+@Composable
+private fun PokemonHeaderImage(pokemonName: String?, modifier: Modifier) {
+    val pokemonImageUrl =
+        if (pokemonName != null) {
+            getPokemonImageUrlByName(pokemonName).toString()
+        } else null
+    if (PokedexFeatureFlags.UseCoil) {
+        AsyncImage(
+            modifier = modifier,
+            model =
+                ImageRequest.Builder(LocalContext.current)
+                    .data(pokemonImageUrl)
+                    .crossfade(PokemonHeaderImageCrossfadeDurationMillis)
+                    .build(),
+            contentDescription = pokemonName,
+            contentScale = ContentScale.Inside,
+            placeholder = painterResource(id = R.drawable.pokemon_preview),
+        )
+    } else {
+        GlideImage(
+            modifier = modifier,
+            model = pokemonImageUrl,
+            contentScale = ContentScale.Inside,
+            transition = CrossFade(tween(PokemonHeaderImageCrossfadeDurationMillis)),
+            contentDescription = pokemonName,
+            loading = placeholder(painterResource(id = R.drawable.pokemon_preview)),
+        )
+    }
+}
+
 @Composable
 private fun DetailsInfo(pokemonInfo: PokemonInfo) {
     Row(
@@ -253,9 +303,7 @@
 }
 
 @Composable
-private fun DetailsStatus(
-    pokemonInfo: PokemonInfo,
-) {
+private fun DetailsStatus(pokemonInfo: PokemonInfo) {
     Text(
         modifier = Modifier.fillMaxWidth().padding(top = 22.dp, bottom = 16.dp),
         text = stringResource(id = R.string.base_stats),
@@ -305,9 +353,7 @@
 @Preview(uiMode = Configuration.UI_MODE_NIGHT_YES)
 @Composable
 private fun PokedexDetailsStatusPreview() {
-    PokedexPreviewTheme {
-        DetailsStatus(
-            pokemonInfo = PreviewUtils.mockPokemonInfo(),
-        )
-    }
+    PokedexPreviewTheme { DetailsStatus(pokemonInfo = PreviewUtils.mockPokemonInfo()) }
 }
+
+private const val PokemonHeaderImageCrossfadeDurationMillis = 250
diff --git a/app/src/main/kotlin/com/skydoves/pokedex/compose/feature/details/PokemonInfoItem.kt b/app/src/main/kotlin/com/skydoves/pokedex/compose/feature/details/PokemonInfoItem.kt
index c2fd3b9..bff4445 100644
--- a/app/src/main/kotlin/com/skydoves/pokedex/compose/feature/details/PokemonInfoItem.kt
+++ b/app/src/main/kotlin/com/skydoves/pokedex/compose/feature/details/PokemonInfoItem.kt
@@ -28,10 +28,7 @@
 import com.skydoves.pokedex.compose.core.designsystem.theme.PokedexTheme
 
 @Composable
-internal fun PokemonInfoItem(
-    title: String?,
-    content: String?,
-) {
+internal fun PokemonInfoItem(title: String?, content: String?) {
     Column(horizontalAlignment = Alignment.CenterHorizontally) {
         PokedexText(
             modifier = Modifier.padding(10.dp),
diff --git a/app/src/main/kotlin/com/skydoves/pokedex/compose/feature/details/PokemonStatusItem.kt b/app/src/main/kotlin/com/skydoves/pokedex/compose/feature/details/PokemonStatusItem.kt
index 8b6f05e..653179b 100644
--- a/app/src/main/kotlin/com/skydoves/pokedex/compose/feature/details/PokemonStatusItem.kt
+++ b/app/src/main/kotlin/com/skydoves/pokedex/compose/feature/details/PokemonStatusItem.kt
@@ -31,14 +31,8 @@
 import com.skydoves.pokedex.compose.core.designsystem.theme.PokedexTheme
 
 @Composable
-internal fun PokemonStatusItem(
-    modifier: Modifier = Modifier,
-    pokedexStatus: PokedexStatus,
-) {
-    Row(
-        modifier = modifier,
-        horizontalArrangement = Arrangement.SpaceEvenly,
-    ) {
+internal fun PokemonStatusItem(modifier: Modifier = Modifier, pokedexStatus: PokedexStatus) {
+    Row(modifier = modifier, horizontalArrangement = Arrangement.SpaceEvenly) {
         Text(
             modifier = Modifier.padding(start = 32.dp).widthIn(min = 20.dp),
             text = pokedexStatus.type,
diff --git a/app/src/main/kotlin/com/skydoves/pokedex/compose/feature/home/PokedexHome.kt b/app/src/main/kotlin/com/skydoves/pokedex/compose/feature/home/PokedexHome.kt
index adf454e..5cc9c28 100644
--- a/app/src/main/kotlin/com/skydoves/pokedex/compose/feature/home/PokedexHome.kt
+++ b/app/src/main/kotlin/com/skydoves/pokedex/compose/feature/home/PokedexHome.kt
@@ -23,6 +23,7 @@
 import androidx.compose.animation.AnimatedVisibilityScope
 import androidx.compose.animation.ExperimentalSharedTransitionApi
 import androidx.compose.animation.SharedTransitionScope
+import androidx.compose.animation.core.tween
 import androidx.compose.foundation.clickable
 import androidx.compose.foundation.layout.Box
 import androidx.compose.foundation.layout.Column
@@ -37,7 +38,6 @@
 import androidx.compose.foundation.lazy.grid.rememberLazyGridState
 import androidx.compose.foundation.shape.RoundedCornerShape
 import androidx.compose.material3.Card
-import androidx.compose.material3.CardColors
 import androidx.compose.material3.CardDefaults
 import androidx.compose.material3.Text
 import androidx.compose.runtime.Composable
@@ -49,9 +49,12 @@
 import androidx.compose.ui.Alignment
 import androidx.compose.ui.Modifier
 import androidx.compose.ui.layout.ContentScale
+import androidx.compose.ui.platform.LocalContext
 import androidx.compose.ui.platform.LocalInspectionMode
 import androidx.compose.ui.platform.testTag
 import androidx.compose.ui.res.painterResource
+import androidx.compose.ui.semantics.semantics
+import androidx.compose.ui.semantics.testTag
 import androidx.compose.ui.text.font.FontWeight
 import androidx.compose.ui.text.style.TextAlign
 import androidx.compose.ui.tooling.preview.Preview
@@ -60,11 +63,15 @@
 import androidx.lifecycle.compose.collectAsStateWithLifecycle
 import androidx.lifecycle.viewmodel.compose.viewModel
 import androidx.palette.graphics.Palette
+import coil3.compose.AsyncImage
+import coil3.request.ImageRequest
+import coil3.request.crossfade
 import com.bumptech.glide.integration.compose.CrossFade
 import com.bumptech.glide.integration.compose.ExperimentalGlideComposeApi
 import com.bumptech.glide.integration.compose.GlideImage
 import com.bumptech.glide.integration.compose.placeholder
 import com.skydoves.pokedex.compose.R
+import com.skydoves.pokedex.compose.core.PokedexFeatureFlags
 import com.skydoves.pokedex.compose.core.data.repository.home.FakeHomeRepository
 import com.skydoves.pokedex.compose.core.designsystem.component.PokedexAppBar
 import com.skydoves.pokedex.compose.core.designsystem.component.PokedexCircularProgress
@@ -76,15 +83,14 @@
 import com.skydoves.pokedex.compose.core.navigation.currentComposeNavigator
 import com.skydoves.pokedex.compose.core.preview.PokedexPreviewTheme
 import com.skydoves.pokedex.compose.core.preview.PreviewUtils
-import com.skydoves.pokedex.compose.core.viewmodel.LocalPokedexViewModelFactory
 import kotlinx.collections.immutable.ImmutableList
 import kotlinx.collections.immutable.toImmutableList
 
 @Composable
 fun PokedexHome(
-    sharedTransitionScope: SharedTransitionScope,
+    sharedTransitionScope: SharedTransitionScope?,
     animatedVisibilityScope: AnimatedVisibilityScope,
-    homeViewModel: HomeViewModel = viewModel(factory = LocalPokedexViewModelFactory.current),
+    homeViewModel: HomeViewModel,
 ) {
     val uiState by homeViewModel.uiState.collectAsStateWithLifecycle()
     val pokemonList by homeViewModel.pokemonList.collectAsStateWithLifecycle()
@@ -104,19 +110,24 @@
 
 @Composable
 private fun HomeContent(
-    sharedTransitionScope: SharedTransitionScope,
+    sharedTransitionScope: SharedTransitionScope?,
     animatedVisibilityScope: AnimatedVisibilityScope,
     uiState: HomeUiState,
     pokemonList: ImmutableList<Pokemon>,
     fetchNextPokemonList: () -> Unit,
 ) {
+    if (sharedTransitionScope != null) {
+        val statusText =
+            "pokedex-home-transition-active-${sharedTransitionScope.isTransitionActive}"
+        Text(statusText, Modifier.semantics { testTag = statusText })
+    }
     Box(modifier = Modifier.fillMaxSize()) {
         val gridState = rememberLazyGridState()
-        LaunchedEffect(gridState) {
+        LaunchedEffect(gridState, pokemonList) {
             val paginationThreshold = pokemonList.size - PaginationBufferSize
             snapshotFlow { gridState.firstVisibleItemIndex >= paginationThreshold }
-                .collect {
-                    if (uiState != HomeUiState.Loading) {
+                .collect { shouldFetchNewItems ->
+                    if (shouldFetchNewItems) {
                         fetchNextPokemonList()
                     }
                 }
@@ -145,7 +156,7 @@
 @OptIn(ExperimentalGlideComposeApi::class)
 @Composable
 private fun PokemonCard(
-    sharedTransitionScope: SharedTransitionScope,
+    sharedTransitionScope: SharedTransitionScope?,
     animatedVisibilityScope: AnimatedVisibilityScope,
     pokemon: Pokemon,
 ) {
@@ -155,12 +166,12 @@
 
     Card(
         modifier =
-            Modifier.padding(6.dp).fillMaxWidth().testTag("Pokemon").clickable {
+            Modifier.padding(6.dp).fillMaxWidth().testTag("${pokemon.name}_card").clickable {
                 composeNavigator.navigate(PokedexScreen.Details(pokemon = pokemon))
             },
         shape = RoundedCornerShape(14.dp),
         colors =
-            CardColors(
+            CardDefaults.cardColors(
                 containerColor = backgroundColor,
                 contentColor = backgroundColor,
                 disabledContainerColor = backgroundColor,
@@ -168,41 +179,51 @@
             ),
         elevation = CardDefaults.cardElevation(defaultElevation = 4.dp),
     ) {
-        GlideImage(
+        PokemonCardImage(
             modifier =
                 Modifier.align(Alignment.CenterHorizontally)
                     .padding(top = 20.dp)
                     .size(120.dp)
-                    .pokedexSharedElement(
-                        sharedTransitionScope = sharedTransitionScope,
-                        isLocalInspectionMode = LocalInspectionMode.current,
-                        state =
-                            sharedTransitionScope.rememberSharedContentState(
-                                key = "image-${pokemon.name}"
-                            ),
-                        animatedVisibilityScope = animatedVisibilityScope,
-                        boundsTransform = boundsTransform,
+                    .then(
+                        if (
+                            sharedTransitionScope != null &&
+                                PokedexFeatureFlags.EnableSharedElementTransitions
+                        ) {
+                            Modifier.pokedexSharedElement(
+                                sharedTransitionScope = sharedTransitionScope,
+                                isLocalInspectionMode = LocalInspectionMode.current,
+                                state =
+                                    sharedTransitionScope.rememberSharedContentState(
+                                        key = "image-${pokemon.name}"
+                                    ),
+                                animatedVisibilityScope = animatedVisibilityScope,
+                                boundsTransform = boundsTransform,
+                            )
+                        } else Modifier
                     ),
-            contentDescription = pokemon.name,
-            model = pokemon.imageUrl,
-            contentScale = ContentScale.Inside,
-            transition = CrossFade,
-            loading = placeholder(painterResource(id = R.drawable.pokemon_preview)),
+            pokemon = pokemon,
         )
 
         Text(
             modifier =
                 Modifier.align(Alignment.CenterHorizontally)
                     .fillMaxWidth()
-                    .pokedexSharedElement(
-                        sharedTransitionScope = sharedTransitionScope,
-                        isLocalInspectionMode = LocalInspectionMode.current,
-                        state =
-                            sharedTransitionScope.rememberSharedContentState(
-                                key = "name-${pokemon.name}"
-                            ),
-                        animatedVisibilityScope = animatedVisibilityScope,
-                        boundsTransform = boundsTransform,
+                    .then(
+                        if (
+                            sharedTransitionScope != null &&
+                                PokedexFeatureFlags.EnableSharedElementTransitions
+                        ) {
+                            Modifier.pokedexSharedElement(
+                                sharedTransitionScope = sharedTransitionScope,
+                                isLocalInspectionMode = LocalInspectionMode.current,
+                                state =
+                                    sharedTransitionScope.rememberSharedContentState(
+                                        key = "name-${pokemon.name}"
+                                    ),
+                                animatedVisibilityScope = animatedVisibilityScope,
+                                boundsTransform = boundsTransform,
+                            )
+                        } else Modifier
                     )
                     .padding(12.dp),
             text = pokemon.name,
@@ -214,6 +235,33 @@
     }
 }
 
+@Composable
+@OptIn(ExperimentalGlideComposeApi::class)
+private fun PokemonCardImage(pokemon: Pokemon, modifier: Modifier = Modifier) {
+    if (PokedexFeatureFlags.UseCoil) {
+        AsyncImage(
+            modifier = modifier,
+            contentDescription = pokemon.name,
+            model =
+                ImageRequest.Builder(LocalContext.current)
+                    .data(pokemon.imageUrl)
+                    .crossfade(PokemonCardImageCrossfadeDurationMillis)
+                    .build(),
+            contentScale = ContentScale.Inside,
+            placeholder = painterResource(id = R.drawable.pokemon_preview),
+        )
+    } else {
+        GlideImage(
+            modifier = modifier,
+            contentDescription = pokemon.name,
+            model = pokemon.imageUrl,
+            contentScale = ContentScale.Inside,
+            transition = CrossFade(tween(PokemonCardImageCrossfadeDurationMillis)),
+            loading = placeholder(painterResource(id = R.drawable.pokemon_preview)),
+        )
+    }
+}
+
 @Preview
 @Preview(uiMode = Configuration.UI_MODE_NIGHT_YES)
 @Composable
@@ -225,7 +273,7 @@
                     animatedVisibilityScope = this,
                     sharedTransitionScope = this@SharedTransitionScope,
                     homeViewModel =
-                        viewModel { HomeViewModel(homeRepository = FakeHomeRepository()) }
+                        viewModel { HomeViewModel(homeRepository = FakeHomeRepository()) },
                 )
             }
         }
@@ -248,4 +296,5 @@
     }
 }
 
-private const val PaginationBufferSize = 8
+private const val PaginationBufferSize = 48
+private const val PokemonCardImageCrossfadeDurationMillis = 250
diff --git a/app/src/main/kotlin/com/skydoves/pokedex/compose/navigation/PokedexNavHost.kt b/app/src/main/kotlin/com/skydoves/pokedex/compose/navigation/PokedexNavHost.kt
index ea11813..813dbdd 100644
--- a/app/src/main/kotlin/com/skydoves/pokedex/compose/navigation/PokedexNavHost.kt
+++ b/app/src/main/kotlin/com/skydoves/pokedex/compose/navigation/PokedexNavHost.kt
@@ -18,20 +18,52 @@
 
 import androidx.compose.animation.ExperimentalSharedTransitionApi
 import androidx.compose.animation.SharedTransitionLayout
+import androidx.compose.animation.SharedTransitionScope
 import androidx.compose.runtime.Composable
+import androidx.compose.runtime.remember
 import androidx.navigation.NavHostController
 import androidx.navigation.compose.NavHost
+import com.skydoves.pokedex.compose.core.PokedexFeatureFlags
 import com.skydoves.pokedex.compose.core.navigation.PokedexScreen
+import com.skydoves.pokedex.compose.core.navigation.navigationEnterTransition
+import com.skydoves.pokedex.compose.core.navigation.navigationExitTransition
+import com.skydoves.pokedex.compose.core.network.di.ModuleLocator
+import com.skydoves.pokedex.compose.core.viewmodel.pokedexViewModelFactory
 
 @OptIn(ExperimentalSharedTransitionApi::class)
 @Composable
-fun PokedexNavHost(navHostController: NavHostController) {
-    SharedTransitionLayout {
-        NavHost(
-            navController = navHostController,
-            startDestination = PokedexScreen.Home,
-        ) {
-            pokedexNavigation(this@SharedTransitionLayout)
+fun PokedexNavHost(navHostController: NavHostController, startDestination: PokedexScreen) {
+    if (PokedexFeatureFlags.EnableSharedTransitionScope) {
+        SharedTransitionLayout {
+            PokedexNavigation(
+                navHostController,
+                sharedTransitionScope = this@SharedTransitionLayout,
+                startDestination = startDestination,
+            )
         }
+    } else {
+        PokedexNavigation(
+            navHostController,
+            sharedTransitionScope = null,
+            startDestination = startDestination,
+        )
+    }
+}
+
+@OptIn(ExperimentalSharedTransitionApi::class)
+@Composable
+private fun PokedexNavigation(
+    navHostController: NavHostController,
+    sharedTransitionScope: SharedTransitionScope?,
+    startDestination: PokedexScreen,
+) {
+    val viewModelFactory = remember { pokedexViewModelFactory(ModuleLocator.repositoryModule) }
+    NavHost(
+        navController = navHostController,
+        startDestination = startDestination.asRoute(),
+        enterTransition = { navigationEnterTransition },
+        exitTransition = { navigationExitTransition },
+    ) {
+        pokedexNavigation(sharedTransitionScope = sharedTransitionScope, viewModelFactory)
     }
 }
diff --git a/app/src/main/kotlin/com/skydoves/pokedex/compose/navigation/PokedexNavigation.kt b/app/src/main/kotlin/com/skydoves/pokedex/compose/navigation/PokedexNavigation.kt
index e31a779..e546460 100644
--- a/app/src/main/kotlin/com/skydoves/pokedex/compose/navigation/PokedexNavigation.kt
+++ b/app/src/main/kotlin/com/skydoves/pokedex/compose/navigation/PokedexNavigation.kt
@@ -16,26 +16,55 @@
 
 package com.skydoves.pokedex.compose.navigation
 
+import androidx.compose.animation.AnimatedContentScope
 import androidx.compose.animation.ExperimentalSharedTransitionApi
 import androidx.compose.animation.SharedTransitionScope
+import androidx.compose.material3.Text
+import androidx.compose.runtime.Composable
+import androidx.compose.ui.Modifier
+import androidx.compose.ui.semantics.semantics
+import androidx.compose.ui.semantics.testTag
+import androidx.lifecycle.ViewModelProvider
+import androidx.lifecycle.viewmodel.compose.viewModel
 import androidx.navigation.NavGraphBuilder
 import androidx.navigation.compose.composable
+import com.skydoves.pokedex.compose.core.designsystem.utils.TraceAsync
 import com.skydoves.pokedex.compose.core.navigation.PokedexScreen
 import com.skydoves.pokedex.compose.feature.details.PokedexDetails
 import com.skydoves.pokedex.compose.feature.home.PokedexHome
 
 @OptIn(ExperimentalSharedTransitionApi::class)
-fun NavGraphBuilder.pokedexNavigation(sharedTransitionScope: SharedTransitionScope) {
-    composable<PokedexScreen.Home> {
-        PokedexHome(sharedTransitionScope = sharedTransitionScope, animatedVisibilityScope = this)
-    }
-
-    composable<PokedexScreen.Details>(
-        typeMap = PokedexScreen.Details.typeMap,
-    ) {
-        PokedexDetails(
+fun NavGraphBuilder.pokedexNavigation(
+    sharedTransitionScope: SharedTransitionScope?,
+    viewModelFactory: ViewModelProvider.Factory,
+) {
+    composable(PokedexScreen.Home.NAVIGATION_ROUTE) {
+        TrackTransitionStatus("home")
+        if (this.transition.isRunning) {
+            TraceAsync("Pokedex Home Navigation Transition")
+        }
+        PokedexHome(
             sharedTransitionScope = sharedTransitionScope,
-            animatedVisibilityScope = this
+            animatedVisibilityScope = this@composable,
+            homeViewModel = viewModel(factory = viewModelFactory),
         )
     }
+
+    composable(PokedexScreen.Details.NAVIGATION_ROUTE) { backStackEntry ->
+        TrackTransitionStatus("details")
+        if (this.transition.isRunning) {
+            TraceAsync("Pokedex Details Navigation Transition")
+        }
+        PokedexDetails(
+            sharedTransitionScope = sharedTransitionScope,
+            animatedVisibilityScope = this@composable,
+            detailsViewModel = viewModel(factory = viewModelFactory),
+        )
+    }
+}
+
+@Composable
+private fun AnimatedContentScope.TrackTransitionStatus(tag: String) {
+    val status = "pokedex-$tag-transition-active-${[email protected]}"
+    Text(text = status, Modifier.semantics { testTag = status })
 }
diff --git a/app/src/main/kotlin/com/skydoves/pokedex/compose/ui/PokedexGlideAppModule.kt b/app/src/main/kotlin/com/skydoves/pokedex/compose/ui/PokedexComposeGlideAppModule.kt
similarity index 90%
rename from app/src/main/kotlin/com/skydoves/pokedex/compose/ui/PokedexGlideAppModule.kt
rename to app/src/main/kotlin/com/skydoves/pokedex/compose/ui/PokedexComposeGlideAppModule.kt
index 12a9330..5c4b35d 100644
--- a/app/src/main/kotlin/com/skydoves/pokedex/compose/ui/PokedexGlideAppModule.kt
+++ b/app/src/main/kotlin/com/skydoves/pokedex/compose/ui/PokedexComposeGlideAppModule.kt
@@ -22,19 +22,19 @@
 import com.bumptech.glide.annotation.GlideModule
 import com.bumptech.glide.integration.okhttp3.OkHttpUrlLoader
 import com.bumptech.glide.load.model.GlideUrl
-import com.bumptech.glide.module.AppGlideModule
+import com.bumptech.glide.module.LibraryGlideModule
 import com.skydoves.pokedex.compose.core.network.di.ModuleLocator
 import java.io.InputStream
 
 @GlideModule
-class PokedexGlideAppModule : AppGlideModule() {
+class PokedexComposeGlideAppModule : LibraryGlideModule() {
     override fun registerComponents(context: Context, glide: Glide, registry: Registry) {
         val okHttpClient = ModuleLocator.networkModule.okHttpClientFactory()
         val loaderFactory = OkHttpUrlLoader.Factory(okHttpClient)
         registry.replace<GlideUrl, InputStream>(
             /* modelClass = */ GlideUrl::class.java,
             /* dataClass = */ InputStream::class.java,
-            /* factory = */ loaderFactory
+            /* factory = */ loaderFactory,
         )
     }
 }
diff --git a/app/src/main/kotlin/com/skydoves/pokedex/compose/ui/PokedexMain.kt b/app/src/main/kotlin/com/skydoves/pokedex/compose/ui/PokedexMain.kt
index 4613103..7b19923 100644
--- a/app/src/main/kotlin/com/skydoves/pokedex/compose/ui/PokedexMain.kt
+++ b/app/src/main/kotlin/com/skydoves/pokedex/compose/ui/PokedexMain.kt
@@ -26,6 +26,10 @@
 import androidx.compose.runtime.remember
 import androidx.compose.ui.platform.LocalContext
 import androidx.navigation.compose.rememberNavController
+import coil3.ImageLoader
+import coil3.compose.setSingletonImageLoaderFactory
+import coil3.network.okhttp.OkHttpNetworkFetcherFactory
+import com.skydoves.pokedex.compose.core.PokedexFeatureFlags
 import com.skydoves.pokedex.compose.core.designsystem.theme.PokedexTheme
 import com.skydoves.pokedex.compose.core.navigation.AppComposeNavigator
 import com.skydoves.pokedex.compose.core.navigation.LocalComposeNavigator
@@ -36,21 +40,43 @@
 
 @Composable
 fun PokedexMain(
-    composeNavigator: AppComposeNavigator<PokedexScreen> = remember { PokedexComposeNavigator() }
+    composeNavigator: AppComposeNavigator<PokedexScreen> = remember { PokedexComposeNavigator() },
+    startDestination: PokedexScreen,
 ) {
     PokedexTheme {
         CompositionLocalProvider(LocalComposeNavigator provides composeNavigator) {
             val context = LocalContext.current
             DisposableEffect(context) {
                 (context as? ComponentActivity)?.enableEdgeToEdge()
-                Trace.beginSection("ModuleLocator.attach")
-                ModuleLocator.attach(context = { context })
-                Trace.endSection()
                 onDispose { ModuleLocator.detach() }
             }
+            Trace.beginSection("ModuleLocator.attach")
+            ModuleLocator.attach(context = { context })
+            Trace.endSection()
+            if (PokedexFeatureFlags.UseCoil) {
+                ConfigureCoil()
+            }
             val navHostController = rememberNavController()
             LaunchedEffect(Unit) { composeNavigator.handleNavigationCommands(navHostController) }
-            PokedexNavHost(navHostController = navHostController)
+            PokedexNavHost(
+                navHostController = navHostController,
+                startDestination = startDestination,
+            )
         }
     }
 }
+
+@Composable
+private fun ConfigureCoil() {
+    setSingletonImageLoaderFactory { context ->
+        ImageLoader.Builder(context)
+            .components {
+                add(
+                    OkHttpNetworkFetcherFactory(
+                        callFactory = ModuleLocator.networkModule.okHttpClient.newBuilder().build()
+                    )
+                )
+            }
+            .build()
+    }
+}