diff --git a/experimental/examples/imageviewer/.gitignore b/experimental/examples/imageviewer/.gitignore
new file mode 100644
index 0000000000..a32b16597b
--- /dev/null
+++ b/experimental/examples/imageviewer/.gitignore
@@ -0,0 +1,15 @@
+*.iml
+.gradle
+/local.properties
+/.idea
+/.idea/caches
+/.idea/libraries
+/.idea/modules.xml
+/.idea/workspace.xml
+/.idea/navEditor.xml
+/.idea/assetWizardSettings.xml
+.DS_Store
+build/
+/captures
+.externalNativeBuild
+.cxx
diff --git a/experimental/examples/imageviewer/.run/desktop.run.xml b/experimental/examples/imageviewer/.run/desktop.run.xml
new file mode 100755
index 0000000000..d9335c1be5
--- /dev/null
+++ b/experimental/examples/imageviewer/.run/desktop.run.xml
@@ -0,0 +1,21 @@
+
+
+
+
+
+
+
+
+
+
+
+ true
+
+
+
\ No newline at end of file
diff --git a/experimental/examples/imageviewer/README.md b/experimental/examples/imageviewer/README.md
new file mode 100755
index 0000000000..3d79c52613
--- /dev/null
+++ b/experimental/examples/imageviewer/README.md
@@ -0,0 +1,18 @@
+An example of image gallery for remote server image viewing, based on Jetpack Compose UI library (desktop and android).
+
+### Running desktop application
+ * To run, launch command: `./gradlew :desktop:run`
+ * Or choose **desktop** configuration in IDE and run it.
+ ![desktop-run-configuration.png](screenshots/desktop-run-configuration.png)
+
+### Building native desktop distribution
+```
+./gradlew :desktop:packageDistributionForCurrentOS
+# outputs are written to desktop/build/compose/binaries
+```
+
+### Running Android application
+
+Open project in IntelliJ IDEA or Android Studio and run "android" configuration.
+
+![Desktop](screenshots/imageviewer.png)
diff --git a/experimental/examples/imageviewer/android/build.gradle.kts b/experimental/examples/imageviewer/android/build.gradle.kts
new file mode 100755
index 0000000000..d5807ca5f3
--- /dev/null
+++ b/experimental/examples/imageviewer/android/build.gradle.kts
@@ -0,0 +1,26 @@
+plugins {
+ id("com.android.application")
+ kotlin("android")
+ id("org.jetbrains.compose")
+}
+
+android {
+ compileSdk = 32
+
+ defaultConfig {
+ minSdk = 26
+ targetSdk = 32
+ versionCode = 1
+ versionName = "1.0"
+ }
+
+ compileOptions {
+ sourceCompatibility = JavaVersion.VERSION_11
+ targetCompatibility = JavaVersion.VERSION_11
+ }
+}
+
+dependencies {
+ implementation(project(":common"))
+ implementation("androidx.activity:activity-compose:1.5.0")
+}
diff --git a/experimental/examples/imageviewer/android/src/main/AndroidManifest.xml b/experimental/examples/imageviewer/android/src/main/AndroidManifest.xml
new file mode 100755
index 0000000000..5b1501c058
--- /dev/null
+++ b/experimental/examples/imageviewer/android/src/main/AndroidManifest.xml
@@ -0,0 +1,28 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/experimental/examples/imageviewer/android/src/main/java/example/imageviewer/MainActivity.kt b/experimental/examples/imageviewer/android/src/main/java/example/imageviewer/MainActivity.kt
new file mode 100755
index 0000000000..53bb8c6160
--- /dev/null
+++ b/experimental/examples/imageviewer/android/src/main/java/example/imageviewer/MainActivity.kt
@@ -0,0 +1,23 @@
+package example.imageviewer
+
+import android.os.Bundle
+import androidx.appcompat.app.AppCompatActivity
+import androidx.activity.compose.setContent
+import example.imageviewer.view.AppUI
+import example.imageviewer.model.ContentState
+import example.imageviewer.model.ImageRepository
+
+class MainActivity : AppCompatActivity() {
+ override fun onCreate(savedInstanceState: Bundle?) {
+ super.onCreate(savedInstanceState)
+
+ val content = ContentState.applyContent(
+ this@MainActivity,
+ "https://raw.githubusercontent.com/JetBrains/compose-jb/master/artwork/imageviewerrepo/fetching.list"
+ )
+
+ setContent {
+ AppUI(content)
+ }
+ }
+}
\ No newline at end of file
diff --git a/experimental/examples/imageviewer/build.gradle.kts b/experimental/examples/imageviewer/build.gradle.kts
new file mode 100755
index 0000000000..bf95fe23b1
--- /dev/null
+++ b/experimental/examples/imageviewer/build.gradle.kts
@@ -0,0 +1,18 @@
+plugins {
+ // this is necessary to avoid the plugins to be loaded multiple times
+ // in each subproject's classloader
+ kotlin("jvm") apply false
+ kotlin("multiplatform") apply false
+ kotlin("android") apply false
+ id("com.android.application") apply false
+ id("com.android.library") apply false
+ id("org.jetbrains.compose") apply false
+}
+
+subprojects {
+ repositories {
+ google()
+ mavenCentral()
+ maven("https://maven.pkg.jetbrains.space/public/p/compose/dev")
+ }
+}
diff --git a/experimental/examples/imageviewer/common/build.gradle.kts b/experimental/examples/imageviewer/common/build.gradle.kts
new file mode 100755
index 0000000000..3a67917bbe
--- /dev/null
+++ b/experimental/examples/imageviewer/common/build.gradle.kts
@@ -0,0 +1,54 @@
+plugins {
+ id("com.android.library")
+ kotlin("multiplatform")
+ id("org.jetbrains.compose")
+}
+
+kotlin {
+ android()
+ jvm("desktop")
+ sourceSets {
+ named("commonMain") {
+ dependencies {
+ api(compose.runtime)
+ api(compose.foundation)
+ api(compose.material)
+ implementation("io.ktor:ktor-client-core:1.4.1")
+ }
+ }
+ named("androidMain") {
+ dependencies {
+ api("androidx.appcompat:appcompat:1.5.1")
+ api("androidx.core:core-ktx:1.8.0")
+ implementation("io.ktor:ktor-client-cio:1.4.1")
+ }
+ }
+ named("desktopMain") {
+ dependencies {
+ api(compose.desktop.common)
+ implementation("io.ktor:ktor-client-cio:1.4.1")
+ }
+ }
+ }
+}
+
+android {
+ compileSdk = 32
+
+ defaultConfig {
+ minSdk = 26
+ targetSdk = 32
+ }
+
+ compileOptions {
+ sourceCompatibility = JavaVersion.VERSION_11
+ targetCompatibility = JavaVersion.VERSION_11
+ }
+
+ sourceSets {
+ named("main") {
+ manifest.srcFile("src/androidMain/AndroidManifest.xml")
+ res.srcDirs("src/androidMain/res")
+ }
+ }
+}
diff --git a/experimental/examples/imageviewer/common/src/androidMain/AndroidManifest.xml b/experimental/examples/imageviewer/common/src/androidMain/AndroidManifest.xml
new file mode 100755
index 0000000000..7a302a744c
--- /dev/null
+++ b/experimental/examples/imageviewer/common/src/androidMain/AndroidManifest.xml
@@ -0,0 +1,2 @@
+
+
\ No newline at end of file
diff --git a/experimental/examples/imageviewer/common/src/androidMain/kotlin/example/imageviewer/core/BitmapFilter.kt b/experimental/examples/imageviewer/common/src/androidMain/kotlin/example/imageviewer/core/BitmapFilter.kt
new file mode 100755
index 0000000000..bf5d0b8c88
--- /dev/null
+++ b/experimental/examples/imageviewer/common/src/androidMain/kotlin/example/imageviewer/core/BitmapFilter.kt
@@ -0,0 +1,7 @@
+package example.imageviewer.core
+
+import android.graphics.Bitmap
+
+interface BitmapFilter {
+ fun apply(bitmap: Bitmap) : Bitmap
+}
\ No newline at end of file
diff --git a/experimental/examples/imageviewer/common/src/androidMain/kotlin/example/imageviewer/model/AndroidContentState.kt b/experimental/examples/imageviewer/common/src/androidMain/kotlin/example/imageviewer/model/AndroidContentState.kt
new file mode 100644
index 0000000000..00d4b026bc
--- /dev/null
+++ b/experimental/examples/imageviewer/common/src/androidMain/kotlin/example/imageviewer/model/AndroidContentState.kt
@@ -0,0 +1,383 @@
+package example.imageviewer.model
+
+import android.content.Context
+import android.graphics.*
+import android.os.Handler
+import android.os.Looper
+import androidx.compose.runtime.MutableState
+import androidx.compose.runtime.mutableStateOf
+import example.imageviewer.common.R
+import example.imageviewer.core.FilterType
+import example.imageviewer.model.filtration.FiltersManager
+import example.imageviewer.utils.clearCache
+import example.imageviewer.utils.isInternetAvailable
+import example.imageviewer.view.showPopUpMessage
+import java.util.concurrent.ExecutorService
+import java.util.concurrent.Executors
+
+
+object ContentState {
+
+ private lateinit var context: Context
+ private lateinit var repository: ImageRepository
+ private lateinit var uriRepository: String
+
+ fun applyContent(context: Context, uriRepository: String): ContentState {
+ if (this::uriRepository.isInitialized && this.uriRepository == uriRepository) {
+ return this
+ }
+
+ this.context = context
+ this.uriRepository = uriRepository
+ repository = ImageRepository(uriRepository)
+ appliedFilters = FiltersManager(context)
+ isContentReady.value = false
+
+ initData()
+
+ return this
+ }
+
+ private val executor: ExecutorService by lazy { Executors.newFixedThreadPool(2) }
+
+ private val handler: Handler by lazy { Handler(Looper.getMainLooper()) }
+
+ fun getContext(): Context {
+ return context
+ }
+
+ fun getOrientation(): Int {
+ return context.resources.configuration.orientation
+ }
+
+ private val isAppReady = mutableStateOf(false)
+ fun isAppReady(): Boolean {
+ return isAppReady.value
+ }
+
+ private val isContentReady = mutableStateOf(false)
+ fun isContentReady(): Boolean {
+ return isContentReady.value
+ }
+
+ fun getString(id: Int): String {
+ return context.getString(id)
+ }
+
+ // drawable content
+ private val mainImage = mutableStateOf(Bitmap.createBitmap(1, 1, Bitmap.Config.ARGB_8888))
+ private val currentImageIndex = mutableStateOf(0)
+ private val miniatures = Miniatures()
+
+ fun getMiniatures(): List