Browse Source

Add uikit to experimental/examples/imageviewer (#2571)

pull/2572/head
dima.avdeev 2 years ago committed by GitHub
parent
commit
a9ac7634cd
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
  1. 15
      artwork/imageviewerrepo/pictures.json
  2. BIN
      artwork/imageviewerrepo/small/1.jpg
  3. BIN
      artwork/imageviewerrepo/small/10.jpg
  4. BIN
      artwork/imageviewerrepo/small/11.jpg
  5. BIN
      artwork/imageviewerrepo/small/12.jpg
  6. BIN
      artwork/imageviewerrepo/small/13.jpg
  7. BIN
      artwork/imageviewerrepo/small/2.jpg
  8. BIN
      artwork/imageviewerrepo/small/3.jpg
  9. BIN
      artwork/imageviewerrepo/small/4.jpg
  10. BIN
      artwork/imageviewerrepo/small/5.jpg
  11. BIN
      artwork/imageviewerrepo/small/6.jpg
  12. BIN
      artwork/imageviewerrepo/small/7.jpg
  13. BIN
      artwork/imageviewerrepo/small/8.jpg
  14. BIN
      artwork/imageviewerrepo/small/9.jpg
  15. 6
      experimental/examples/imageviewer/.gitignore
  16. 8
      experimental/examples/imageviewer/.run/desktopApp.run.xml
  17. 7
      experimental/examples/imageviewer/.run/iosApp (AndroidStudio).run.xml
  18. 11
      experimental/examples/imageviewer/.run/iosApp.run.xml
  19. 26
      experimental/examples/imageviewer/README.md
  20. 26
      experimental/examples/imageviewer/android/build.gradle.kts
  21. 23
      experimental/examples/imageviewer/android/src/main/java/example/imageviewer/MainActivity.kt
  22. 33
      experimental/examples/imageviewer/androidApp/build.gradle.kts
  23. 1
      experimental/examples/imageviewer/androidApp/src/main/AndroidManifest.xml
  24. 15
      experimental/examples/imageviewer/androidApp/src/main/java/example/imageviewer/MainActivity.kt
  25. 1
      experimental/examples/imageviewer/build.gradle.kts
  26. 54
      experimental/examples/imageviewer/common/build.gradle.kts
  27. 2
      experimental/examples/imageviewer/common/src/androidMain/AndroidManifest.xml
  28. 7
      experimental/examples/imageviewer/common/src/androidMain/kotlin/example/imageviewer/core/BitmapFilter.kt
  29. 383
      experimental/examples/imageviewer/common/src/androidMain/kotlin/example/imageviewer/model/AndroidContentState.kt
  30. 131
      experimental/examples/imageviewer/common/src/androidMain/kotlin/example/imageviewer/model/ImageHandler.kt
  31. 12
      experimental/examples/imageviewer/common/src/androidMain/kotlin/example/imageviewer/model/Picture.kt
  32. 13
      experimental/examples/imageviewer/common/src/androidMain/kotlin/example/imageviewer/model/filtration/BlurFilter.kt
  33. 12
      experimental/examples/imageviewer/common/src/androidMain/kotlin/example/imageviewer/model/filtration/EmptyFilter.kt
  34. 54
      experimental/examples/imageviewer/common/src/androidMain/kotlin/example/imageviewer/model/filtration/FiltersManager.kt
  35. 12
      experimental/examples/imageviewer/common/src/androidMain/kotlin/example/imageviewer/model/filtration/GrayScaleFilter.kt
  36. 12
      experimental/examples/imageviewer/common/src/androidMain/kotlin/example/imageviewer/model/filtration/PixelFilter.kt
  37. 38
      experimental/examples/imageviewer/common/src/androidMain/kotlin/example/imageviewer/style/Decoration.kt
  38. 52
      experimental/examples/imageviewer/common/src/androidMain/kotlin/example/imageviewer/utils/Caching.kt
  39. 9
      experimental/examples/imageviewer/common/src/androidMain/kotlin/example/imageviewer/utils/Coroutines.kt
  40. 195
      experimental/examples/imageviewer/common/src/androidMain/kotlin/example/imageviewer/utils/GraphicsMath.kt
  41. 40
      experimental/examples/imageviewer/common/src/androidMain/kotlin/example/imageviewer/view/AppUI.kt
  42. 197
      experimental/examples/imageviewer/common/src/androidMain/kotlin/example/imageviewer/view/FullscreenImage.kt
  43. 218
      experimental/examples/imageviewer/common/src/androidMain/kotlin/example/imageviewer/view/MainScreen.kt
  44. BIN
      experimental/examples/imageviewer/common/src/androidMain/res/drawable/filter_unknown.png
  45. 18
      experimental/examples/imageviewer/common/src/commonMain/kotlin/example/imageviewer/core/EventLocker.kt
  46. 5
      experimental/examples/imageviewer/common/src/commonMain/kotlin/example/imageviewer/core/Repository.kt
  47. 33
      experimental/examples/imageviewer/common/src/commonMain/kotlin/example/imageviewer/model/ImageRepository.kt
  48. 41
      experimental/examples/imageviewer/common/src/commonMain/kotlin/example/imageviewer/model/Miniatures.kt
  49. 23
      experimental/examples/imageviewer/common/src/commonMain/kotlin/example/imageviewer/model/ScreenType.kt
  50. 16
      experimental/examples/imageviewer/common/src/commonMain/kotlin/example/imageviewer/style/Palette.kt
  51. 7
      experimental/examples/imageviewer/common/src/commonMain/kotlin/example/imageviewer/utils/Coroutines.kt
  52. 37
      experimental/examples/imageviewer/common/src/commonMain/kotlin/example/imageviewer/utils/Network.kt
  53. 21
      experimental/examples/imageviewer/common/src/commonMain/kotlin/example/imageviewer/view/Clickable.kt
  54. 88
      experimental/examples/imageviewer/common/src/commonMain/kotlin/example/imageviewer/view/Draggable.kt
  55. 47
      experimental/examples/imageviewer/common/src/commonMain/kotlin/example/imageviewer/view/Scalable.kt
  56. 27
      experimental/examples/imageviewer/common/src/commonMain/kotlin/example/imageviewer/view/SplashUI.kt
  57. 7
      experimental/examples/imageviewer/common/src/desktopMain/kotlin/example/imageviewer/core/BitmapFilter.kt
  58. 362
      experimental/examples/imageviewer/common/src/desktopMain/kotlin/example/imageviewer/model/DesktopContentState.kt
  59. 130
      experimental/examples/imageviewer/common/src/desktopMain/kotlin/example/imageviewer/model/ImageHandler.kt
  60. 12
      experimental/examples/imageviewer/common/src/desktopMain/kotlin/example/imageviewer/model/Picture.kt
  61. 12
      experimental/examples/imageviewer/common/src/desktopMain/kotlin/example/imageviewer/model/filtration/BlurFilter.kt
  62. 12
      experimental/examples/imageviewer/common/src/desktopMain/kotlin/example/imageviewer/model/filtration/EmptyFilter.kt
  63. 53
      experimental/examples/imageviewer/common/src/desktopMain/kotlin/example/imageviewer/model/filtration/FiltersManager.kt
  64. 12
      experimental/examples/imageviewer/common/src/desktopMain/kotlin/example/imageviewer/model/filtration/GrayScaleFilter.kt
  65. 12
      experimental/examples/imageviewer/common/src/desktopMain/kotlin/example/imageviewer/model/filtration/PixelFilter.kt
  66. 42
      experimental/examples/imageviewer/common/src/desktopMain/kotlin/example/imageviewer/style/Decoration.kt
  67. 53
      experimental/examples/imageviewer/common/src/desktopMain/kotlin/example/imageviewer/utils/Caching.kt
  68. 9
      experimental/examples/imageviewer/common/src/desktopMain/kotlin/example/imageviewer/utils/Coroutines.kt
  69. 206
      experimental/examples/imageviewer/common/src/desktopMain/kotlin/example/imageviewer/utils/GraphicsMath.kt
  70. 40
      experimental/examples/imageviewer/common/src/desktopMain/kotlin/example/imageviewer/view/AppUI.kt
  71. 207
      experimental/examples/imageviewer/common/src/desktopMain/kotlin/example/imageviewer/view/FullscreenImage.kt
  72. 250
      experimental/examples/imageviewer/common/src/desktopMain/kotlin/example/imageviewer/view/MainScreen.kt
  73. 67
      experimental/examples/imageviewer/common/src/desktopMain/kotlin/example/imageviewer/view/Zoomable.kt
  74. BIN
      experimental/examples/imageviewer/common/src/desktopMain/resources/images/back.png
  75. BIN
      experimental/examples/imageviewer/common/src/desktopMain/resources/images/blur_off.png
  76. BIN
      experimental/examples/imageviewer/common/src/desktopMain/resources/images/blur_on.png
  77. BIN
      experimental/examples/imageviewer/common/src/desktopMain/resources/images/dots.png
  78. BIN
      experimental/examples/imageviewer/common/src/desktopMain/resources/images/empty.png
  79. BIN
      experimental/examples/imageviewer/common/src/desktopMain/resources/images/filter_unknown.png
  80. BIN
      experimental/examples/imageviewer/common/src/desktopMain/resources/images/grayscale_off.png
  81. BIN
      experimental/examples/imageviewer/common/src/desktopMain/resources/images/grayscale_on.png
  82. BIN
      experimental/examples/imageviewer/common/src/desktopMain/resources/images/pixel_off.png
  83. BIN
      experimental/examples/imageviewer/common/src/desktopMain/resources/images/pixel_on.png
  84. BIN
      experimental/examples/imageviewer/common/src/desktopMain/resources/images/refresh.png
  85. 59
      experimental/examples/imageviewer/desktop/src/jvmMain/kotlin/example/imageviewer/Main.kt
  86. 6
      experimental/examples/imageviewer/desktopApp/build.gradle.kts
  87. 0
      experimental/examples/imageviewer/desktopApp/rules.pro
  88. 11
      experimental/examples/imageviewer/desktopApp/src/jvmMain/kotlin/example/imageviewer/Main.kt
  89. 36
      experimental/examples/imageviewer/gradle.properties
  90. BIN
      experimental/examples/imageviewer/gradle/wrapper/gradle-wrapper.jar
  91. 263
      experimental/examples/imageviewer/gradlew
  92. 33
      experimental/examples/imageviewer/gradlew.bat
  93. 1
      experimental/examples/imageviewer/iosApp/Configuration/TeamId.xcconfig
  94. 398
      experimental/examples/imageviewer/iosApp/Imageviewer.xcodeproj/project.pbxproj
  95. 5
      experimental/examples/imageviewer/iosApp/Podfile
  96. 48
      experimental/examples/imageviewer/iosApp/iosApp/Info.plist
  97. 15
      experimental/examples/imageviewer/iosApp/iosApp/iosApp.swift
  98. BIN
      experimental/examples/imageviewer/run-configurations.png
  99. 8
      experimental/examples/imageviewer/settings.gradle.kts
  100. 84
      experimental/examples/imageviewer/shared/build.gradle.kts
  101. Some files were not shown because too many files have changed in this diff Show More

15
artwork/imageviewerrepo/pictures.json

@ -0,0 +1,15 @@
[
{"big": "1.jpg", "small": "small/1.jpg"},
{"big": "2.jpg", "small": "small/2.jpg"},
{"big": "3.jpg", "small": "small/3.jpg"},
{"big": "4.jpg", "small": "small/4.jpg"},
{"big": "5.jpg", "small": "small/5.jpg"},
{"big": "6.jpg", "small": "small/6.jpg"},
{"big": "7.jpg", "small": "small/7.jpg"},
{"big": "8.jpg", "small": "small/8.jpg"},
{"big": "9.jpg", "small": "small/9.jpg"},
{"big": "10.jpg", "small": "small/10.jpg"},
{"big": "11.jpg", "small": "small/11.jpg"},
{"big": "12.jpg", "small": "small/12.jpg"},
{"big": "13.jpg", "small": "small/13.jpg"}
]

BIN
artwork/imageviewerrepo/small/1.jpg

Binary file not shown.

After

Width:  |  Height:  |  Size: 31 KiB

BIN
artwork/imageviewerrepo/small/10.jpg

Binary file not shown.

After

Width:  |  Height:  |  Size: 25 KiB

BIN
artwork/imageviewerrepo/small/11.jpg

Binary file not shown.

After

Width:  |  Height:  |  Size: 29 KiB

BIN
artwork/imageviewerrepo/small/12.jpg

Binary file not shown.

After

Width:  |  Height:  |  Size: 35 KiB

BIN
artwork/imageviewerrepo/small/13.jpg

Binary file not shown.

After

Width:  |  Height:  |  Size: 40 KiB

BIN
artwork/imageviewerrepo/small/2.jpg

Binary file not shown.

After

Width:  |  Height:  |  Size: 19 KiB

BIN
artwork/imageviewerrepo/small/3.jpg

Binary file not shown.

After

Width:  |  Height:  |  Size: 37 KiB

BIN
artwork/imageviewerrepo/small/4.jpg

Binary file not shown.

After

Width:  |  Height:  |  Size: 36 KiB

BIN
artwork/imageviewerrepo/small/5.jpg

Binary file not shown.

After

Width:  |  Height:  |  Size: 15 KiB

BIN
artwork/imageviewerrepo/small/6.jpg

Binary file not shown.

After

Width:  |  Height:  |  Size: 47 KiB

BIN
artwork/imageviewerrepo/small/7.jpg

Binary file not shown.

After

Width:  |  Height:  |  Size: 49 KiB

BIN
artwork/imageviewerrepo/small/8.jpg

Binary file not shown.

After

Width:  |  Height:  |  Size: 29 KiB

BIN
artwork/imageviewerrepo/small/9.jpg

Binary file not shown.

After

Width:  |  Height:  |  Size: 26 KiB

6
experimental/examples/imageviewer/.gitignore vendored

@ -13,3 +13,9 @@ build/
/captures
.externalNativeBuild
.cxx
iosApp/Podfile.lock
iosApp/Pods/*
iosApp/Imageviewer.xcworkspace/*
iosApp/Imageviewer.xcodeproj/*
!iosApp/Imageviewer.xcodeproj/project.pbxproj
shared/shared.podspec

8
experimental/examples/imageviewer/.run/desktop.run.xml → experimental/examples/imageviewer/.run/desktopApp.run.xml

@ -1,8 +1,8 @@
<component name="ProjectRunConfigurationManager">
<configuration default="false" name="desktop" type="GradleRunConfiguration" factoryName="Gradle">
<configuration default="false" name="desktopApp" type="GradleRunConfiguration" factoryName="Gradle">
<ExternalSystemSettings>
<option name="executionName" />
<option name="externalProjectPath" value="$PROJECT_DIR$/desktop" />
<option name="externalProjectPath" value="$PROJECT_DIR$/desktopApp" />
<option name="externalSystemIdString" value="GRADLE" />
<option name="scriptParameters" value="" />
<option name="taskDescriptions">
@ -15,7 +15,9 @@
</option>
<option name="vmOptions" value="" />
</ExternalSystemSettings>
<GradleScriptDebugEnabled>true</GradleScriptDebugEnabled>
<ExternalSystemDebugServerProcess>true</ExternalSystemDebugServerProcess>
<ExternalSystemReattachDebugProcess>true</ExternalSystemReattachDebugProcess>
<DebugAllEnabled>false</DebugAllEnabled>
<method v="2" />
</configuration>
</component>

7
experimental/examples/imageviewer/.run/iosApp (AndroidStudio).run.xml

@ -0,0 +1,7 @@
<component name="ProjectRunConfigurationManager">
<configuration default="false" name="iosApp (AndroidStudio)" type="KmmRunConfiguration" factoryName="iOS Application" CONFIG_VERSION="1" XCODE_PROJECT="$PROJECT_DIR$/iosApp/Imageviewer.xcworkspace" XCODE_CONFIGURATION="Debug" XCODE_SCHEME="Imageviewer">
<method v="2">
<option name="com.jetbrains.kmm.ios.BuildIOSAppTask" enabled="true" />
</method>
</configuration>
</component>

11
experimental/examples/imageviewer/.run/iosApp.run.xml

@ -0,0 +1,11 @@
<component name="ProjectRunConfigurationManager">
<configuration default="false" name="iosApp" type="AppleRunConfiguration" factoryName="Application" REDIRECT_INPUT="false" ELEVATE="false" USE_EXTERNAL_CONSOLE="false" PASS_PARENT_ENVS_2="true" PROJECT_NAME="Imageviewer" TARGET_NAME="Imageviewer" CONFIG_NAME="Debug" IS_LOCATION_SIMULATION_SUPPORTED="true" SCHEME_NAME="iosApp" IS_LOCATION_SIMULATION_ALLOWED="true" LOCATION_SCENARIO_ID="com.apple.dt.IDEFoundation.CurrentLocationScenarioIdentifier" LOCATION_SCENARIO_TYPE="1" APPLICATION_LANGUAGE="IDELaunchSchemeLanguageUseSystemLanguage" APPLICATION_REGION="" RUN_TARGET_PROJECT_NAME="Imageviewer" RUN_TARGET_NAME="Imageviewer" MAKE_ACTIVE="TRUE" SHOULD_DEBUG_EXTENSIONS="false">
<EXTENSION ID="org.jetbrains.appcode.reveal.RevealRunConfigurationExtension">
<REVEAL_SETTINGS autoInject="false" autoInstall="true" askToEnableAutoInstall="true" />
</EXTENSION>
<embedded_app_extension_list />
<method v="2">
<option name="com.jetbrains.cidr.execution.CidrBuildBeforeRunTaskProvider$BuildBeforeRunTask" enabled="true" />
</method>
</configuration>
</component>

26
experimental/examples/imageviewer/README.md

@ -1,11 +1,27 @@
An example of image gallery for remote server image viewing, based on Jetpack Compose UI library (desktop and android).
# Imageviewer
### 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)
An example of image gallery for remote server image viewing,
based on Compose Multiplatform UI library (desktop, android and iOS).
## How to run
Choose a run configuration for an appropriate target in IDE and run it.
![run-configurations.png](run-configurations.png)
To run on iOS device, please correct `iosApp/Configuration/TeamId.xcconfig` with your Apple Team ID.
Alternatively, you may setup signing within XCode opening `iosApp/Imageviewer.xcworkspace` and then
using "Signing & Capabilities" tab of `ImageViewer` target.
Then choose **iosApp** configuration in IDE and run it
(may also be referred as `ImageViewer` in the Run Configurations or `iosApp (AndroidStudio)` for Android studio).
## Run on desktop via Gradle
`./gradlew desktopApp:run`
### Building native desktop distribution
```
./gradlew :desktop:packageDistributionForCurrentOS
# outputs are written to desktop/build/compose/binaries

26
experimental/examples/imageviewer/android/build.gradle.kts

@ -1,26 +0,0 @@
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")
}

23
experimental/examples/imageviewer/android/src/main/java/example/imageviewer/MainActivity.kt

@ -1,23 +0,0 @@
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)
}
}
}

33
experimental/examples/imageviewer/androidApp/build.gradle.kts

@ -0,0 +1,33 @@
plugins {
kotlin("multiplatform")
id("com.android.application")
id("org.jetbrains.compose")
}
kotlin {
android()
sourceSets {
val androidMain by getting {
dependencies {
implementation(project(":shared"))
implementation("androidx.appcompat:appcompat:1.5.1")
implementation("androidx.activity:activity-compose:1.6.1")
}
}
}
}
android {
compileSdk = 33
defaultConfig {
applicationId = "org.jetbrains.imageviewer"
minSdk = 24
targetSdk = 33
versionCode = 1
versionName = "1.0"
}
compileOptions {
sourceCompatibility = JavaVersion.VERSION_1_8
targetCompatibility = JavaVersion.VERSION_1_8
}
}

1
experimental/examples/imageviewer/android/src/main/AndroidManifest.xml → experimental/examples/imageviewer/androidApp/src/main/AndroidManifest.xml

@ -5,7 +5,6 @@
<uses-permission android:name="android.permission.INTERNET" />
<uses-permission android:name="android.permission.ACCESS_NETWORK_STATE"/>
<uses-permission android:name="android.permission.READ_EXTERNAL_STORAGE" />
<uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE" />
<application
android:allowBackup="true"

15
experimental/examples/imageviewer/androidApp/src/main/java/example/imageviewer/MainActivity.kt

@ -0,0 +1,15 @@
package example.imageviewer
import android.os.Bundle
import androidx.activity.compose.setContent
import androidx.appcompat.app.AppCompatActivity
import example.imageviewer.view.ImageViewerAndroid
class MainActivity : AppCompatActivity() {
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContent {
ImageViewerAndroid()
}
}
}

1
experimental/examples/imageviewer/build.gradle.kts

@ -14,5 +14,6 @@ allprojects {
google()
mavenCentral()
maven("https://maven.pkg.jetbrains.space/public/p/compose/dev")
mavenLocal()
}
}

54
experimental/examples/imageviewer/common/build.gradle.kts

@ -1,54 +0,0 @@
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")
}
}
}

2
experimental/examples/imageviewer/common/src/androidMain/AndroidManifest.xml

@ -1,2 +0,0 @@
<?xml version="1.0" encoding="utf-8"?>
<manifest package="example.imageviewer.common"/>

7
experimental/examples/imageviewer/common/src/androidMain/kotlin/example/imageviewer/core/BitmapFilter.kt

@ -1,7 +0,0 @@
package example.imageviewer.core
import android.graphics.Bitmap
interface BitmapFilter {
fun apply(bitmap: Bitmap) : Bitmap
}

383
experimental/examples/imageviewer/common/src/androidMain/kotlin/example/imageviewer/model/AndroidContentState.kt

@ -1,383 +0,0 @@
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<Picture> {
return miniatures.getMiniatures()
}
fun getSelectedImage(): Bitmap {
return mainImage.value
}
fun getSelectedImageName(): String {
return MainImageWrapper.getName()
}
// filters managing
private lateinit var appliedFilters: FiltersManager
private val filterUIState: MutableMap<FilterType, MutableState<Boolean>> = LinkedHashMap()
private fun toggleFilterState(filter: FilterType) {
if (!filterUIState.containsKey(filter)) {
filterUIState[filter] = mutableStateOf(true)
} else {
val value = filterUIState[filter]!!.value
filterUIState[filter]!!.value = !value
}
}
fun toggleFilter(filter: FilterType) {
if (containsFilter(filter)) {
removeFilter(filter)
} else {
addFilter(filter)
}
toggleFilterState(filter)
var bitmap = MainImageWrapper.origin
if (bitmap != null) {
bitmap = appliedFilters.applyFilters(bitmap)
MainImageWrapper.setImage(bitmap)
mainImage.value = bitmap
}
}
private fun addFilter(filter: FilterType) {
appliedFilters.add(filter)
MainImageWrapper.addFilter(filter)
}
private fun removeFilter(filter: FilterType) {
appliedFilters.remove(filter)
MainImageWrapper.removeFilter(filter)
}
private fun containsFilter(type: FilterType): Boolean {
return appliedFilters.contains(type)
}
fun isFilterEnabled(type: FilterType): Boolean {
if (!filterUIState.containsKey(type)) {
filterUIState[type] = mutableStateOf(false)
}
return filterUIState[type]!!.value
}
private fun restoreFilters(): Bitmap {
filterUIState.clear()
appliedFilters.clear()
return MainImageWrapper.restore()
}
fun restoreMainImage() {
mainImage.value = restoreFilters()
}
// application content initialization
private fun initData() {
if (isContentReady.value)
return
val directory = context.cacheDir.absolutePath
executor.execute {
try {
if (isInternetAvailable()) {
val imageList = repository.get()
if (imageList.isEmpty()) {
handler.post {
showPopUpMessage(
getString(R.string.repo_invalid),
context
)
onContentReady()
}
return@execute
}
val pictureList = loadImages(directory, imageList)
if (pictureList.isEmpty()) {
handler.post {
showPopUpMessage(
getString(R.string.repo_empty),
context
)
onContentReady()
}
} else {
val picture = loadFullImage(imageList[0])
handler.post {
miniatures.setMiniatures(pictureList)
if (isMainImageEmpty()) {
wrapPictureIntoMainImage(picture)
} else {
appliedFilters.add(MainImageWrapper.getFilters())
mainImage.value = MainImageWrapper.getImage()
currentImageIndex.value = MainImageWrapper.getId()
}
onContentReady()
}
}
} else {
handler.post {
showPopUpMessage(
getString(R.string.no_internet),
context
)
onContentReady()
}
}
} catch (e: Exception) {
e.printStackTrace()
}
}
}
// preview/fullscreen image managing
fun isMainImageEmpty(): Boolean {
return MainImageWrapper.isEmpty()
}
fun fullscreen(picture: Picture) {
isContentReady.value = false
AppState.screenState(ScreenType.FullscreenImage)
setMainImage(picture)
}
fun setMainImage(picture: Picture) {
if (MainImageWrapper.getId() == picture.id) {
if (!isContentReady())
onContentReady()
return
}
isContentReady.value = false
executor.execute {
if (isInternetAvailable()) {
val fullSizePicture = loadFullImage(picture.source)
fullSizePicture.id = picture.id
handler.post {
wrapPictureIntoMainImage(fullSizePicture)
onContentReady()
}
} else {
handler.post {
showPopUpMessage(
"${getString(R.string.no_internet)}\n${getString(R.string.load_image_unavailable)}",
context
)
wrapPictureIntoMainImage(picture)
}
}
}
}
private fun onContentReady() {
isContentReady.value = true
isAppReady.value = true
}
private fun wrapPictureIntoMainImage(picture: Picture) {
MainImageWrapper.wrapPicture(picture)
MainImageWrapper.saveOrigin()
mainImage.value = picture.image
currentImageIndex.value = picture.id
}
fun swipeNext() {
if (currentImageIndex.value == miniatures.size() - 1) {
showPopUpMessage(
getString(R.string.last_image),
context
)
return
}
restoreFilters()
setMainImage(miniatures.get(++currentImageIndex.value))
}
fun swipePrevious() {
if (currentImageIndex.value == 0) {
showPopUpMessage(
getString(R.string.first_image),
context
)
return
}
restoreFilters()
setMainImage(miniatures.get(--currentImageIndex.value))
}
fun refresh() {
executor.execute {
if (isInternetAvailable()) {
handler.post {
clearCache(context)
MainImageWrapper.clear()
miniatures.clear()
isContentReady.value = false
initData()
}
} else {
handler.post {
showPopUpMessage(
"${getString(R.string.no_internet)}\n${getString(R.string.refresh_unavailable)}",
context
)
}
}
}
}
}
private object MainImageWrapper {
// origin image
var origin: Bitmap? = null
private set
fun saveOrigin() {
origin = copy(picture.value.image)
}
fun restore(): Bitmap {
if (origin != null) {
filtersSet.clear()
picture.value.image = copy(origin!!)
}
return copy(picture.value.image)
}
// picture adapter
private var picture = mutableStateOf(
Picture(image = Bitmap.createBitmap(1, 1, Bitmap.Config.ARGB_8888))
)
fun wrapPicture(picture: Picture) {
this.picture.value = picture
}
fun setImage(bitmap: Bitmap) {
picture.value.image = bitmap
}
fun isEmpty(): Boolean {
return (picture.value.name == "")
}
fun clear() {
picture.value = Picture(image = Bitmap.createBitmap(1, 1, Bitmap.Config.ARGB_8888))
}
fun getName(): String {
return picture.value.name
}
fun getImage(): Bitmap {
return picture.value.image
}
fun getId(): Int {
return picture.value.id
}
// applied filters
private var filtersSet: MutableSet<FilterType> = LinkedHashSet()
fun addFilter(filter: FilterType) {
filtersSet.add(filter)
}
fun removeFilter(filter: FilterType) {
filtersSet.remove(filter)
}
fun getFilters(): Set<FilterType> {
return filtersSet
}
private fun copy(bitmap: Bitmap): Bitmap {
return bitmap.copy(bitmap.config, false)
}
}

131
experimental/examples/imageviewer/common/src/androidMain/kotlin/example/imageviewer/model/ImageHandler.kt

@ -1,131 +0,0 @@
package example.imageviewer.model
import android.graphics.Bitmap
import android.graphics.BitmapFactory
import example.imageviewer.utils.cacheImage
import example.imageviewer.utils.cacheImagePostfix
import example.imageviewer.utils.scaleBitmapAspectRatio
import example.imageviewer.utils.toPx
import java.io.File
import java.io.FileInputStream
import java.io.InputStream
import java.io.InputStreamReader
import java.io.BufferedReader
import java.lang.Exception
import java.net.HttpURLConnection
import java.net.URL
import java.nio.charset.StandardCharsets
fun loadFullImage(source: String): Picture {
try {
val url = URL(source)
val connection: HttpURLConnection = url.openConnection() as HttpURLConnection
connection.connectTimeout = 5000
connection.connect()
val input: InputStream = connection.inputStream
val bitmap: Bitmap? = BitmapFactory.decodeStream(input)
if (bitmap != null) {
return Picture(
source = source,
image = bitmap,
name = getNameURL(source),
width = bitmap.width,
height = bitmap.height
)
}
} catch (e: Exception) {
e.printStackTrace()
}
return Picture(image = Bitmap.createBitmap(1, 1, Bitmap.Config.ARGB_8888))
}
fun loadImages(cachePath: String, list: List<String>): MutableList<Picture> {
val result: MutableList<Picture> = ArrayList()
for (source in list) {
val name = getNameURL(source)
val path = cachePath + File.separator + name
if (File(path + "info").exists()) {
addCachedMiniature(filePath = path, outList = result)
} else {
addFreshMiniature(source = source, outList = result, path = cachePath)
}
result.last().id = result.size - 1
}
return result
}
private fun addFreshMiniature(
source: String,
outList: MutableList<Picture>,
path: String
) {
try {
val url = URL(source)
val connection: HttpURLConnection = url.openConnection() as HttpURLConnection
connection.connectTimeout = 5000
connection.connect()
val input: InputStream = connection.inputStream
val result: Bitmap? = BitmapFactory.decodeStream(input)
if (result != null) {
val picture = Picture(
source,
getNameURL(source),
scaleBitmapAspectRatio(result, 200, 164),
result.width,
result.height
)
outList.add(picture)
cacheImage(path + getNameURL(source), picture)
}
} catch (e: Exception) {
e.printStackTrace()
}
}
private fun addCachedMiniature(
filePath: String,
outList: MutableList<Picture>
) {
try {
val read = BufferedReader(
InputStreamReader(
FileInputStream(filePath + cacheImagePostfix),
StandardCharsets.UTF_8
)
)
val source = read.readLine()
val width = read.readLine().toInt()
val height = read.readLine().toInt()
read.close()
val result: Bitmap? = BitmapFactory.decodeFile(filePath)
if (result != null) {
val picture = Picture(
source,
getNameURL(source),
result,
width,
height
)
outList.add(picture)
}
} catch (e: Exception) {
e.printStackTrace()
}
}
private fun getNameURL(url: String): String {
return url.substring(url.lastIndexOf('/') + 1, url.length)
}

12
experimental/examples/imageviewer/common/src/androidMain/kotlin/example/imageviewer/model/Picture.kt

@ -1,12 +0,0 @@
package example.imageviewer.model
import android.graphics.Bitmap
actual data class Picture(
var source: String = "",
var name: String = "",
var image: Bitmap,
var width: Int = 0,
var height: Int = 0,
var id: Int = 0
)

13
experimental/examples/imageviewer/common/src/androidMain/kotlin/example/imageviewer/model/filtration/BlurFilter.kt

@ -1,13 +0,0 @@
package example.imageviewer.model.filtration
import android.content.Context
import android.graphics.Bitmap
import example.imageviewer.core.BitmapFilter
import example.imageviewer.utils.applyBlurFilter
class BlurFilter(private val context: Context) : BitmapFilter {
override fun apply(bitmap: Bitmap): Bitmap {
return applyBlurFilter(bitmap, context)
}
}

12
experimental/examples/imageviewer/common/src/androidMain/kotlin/example/imageviewer/model/filtration/EmptyFilter.kt

@ -1,12 +0,0 @@
package example.imageviewer.model.filtration
import android.graphics.Bitmap
import example.imageviewer.core.BitmapFilter
class EmptyFilter : BitmapFilter {
override fun apply(bitmap: Bitmap): Bitmap {
return bitmap
}
}

54
experimental/examples/imageviewer/common/src/androidMain/kotlin/example/imageviewer/model/filtration/FiltersManager.kt

@ -1,54 +0,0 @@
package example.imageviewer.model.filtration
import android.content.Context
import android.graphics.Bitmap
import example.imageviewer.core.BitmapFilter
import example.imageviewer.core.FilterType
class FiltersManager(private val context: Context) {
private var filtersMap: MutableMap<FilterType, BitmapFilter> = LinkedHashMap()
fun clear() {
filtersMap = LinkedHashMap()
}
fun add(filters: Collection<FilterType>) {
for (filter in filters)
add(filter)
}
fun add(filter: FilterType) {
if (!filtersMap.containsKey(filter))
filtersMap[filter] = getFilter(filter, context)
}
fun remove(filter: FilterType) {
filtersMap.remove(filter)
}
fun contains(filter: FilterType): Boolean {
return filtersMap.contains(filter)
}
fun applyFilters(bitmap: Bitmap): Bitmap {
var result: Bitmap = bitmap
for (filter in filtersMap) {
result = filter.value.apply(result)
}
return result
}
}
private fun getFilter(type: FilterType, context: Context): BitmapFilter {
return when (type) {
FilterType.GrayScale -> GrayScaleFilter()
FilterType.Pixel -> PixelFilter()
FilterType.Blur -> BlurFilter(context)
}
}

12
experimental/examples/imageviewer/common/src/androidMain/kotlin/example/imageviewer/model/filtration/GrayScaleFilter.kt

@ -1,12 +0,0 @@
package example.imageviewer.model.filtration
import android.graphics.Bitmap
import example.imageviewer.core.BitmapFilter
import example.imageviewer.utils.applyGrayScaleFilter
class GrayScaleFilter : BitmapFilter {
override fun apply(bitmap: Bitmap) : Bitmap {
return applyGrayScaleFilter(bitmap)
}
}

12
experimental/examples/imageviewer/common/src/androidMain/kotlin/example/imageviewer/model/filtration/PixelFilter.kt

@ -1,12 +0,0 @@
package example.imageviewer.model.filtration
import android.graphics.Bitmap
import example.imageviewer.core.BitmapFilter
import example.imageviewer.utils.applyPixelFilter
class PixelFilter : BitmapFilter {
override fun apply(bitmap: Bitmap): Bitmap {
return applyPixelFilter(bitmap)
}
}

38
experimental/examples/imageviewer/common/src/androidMain/kotlin/example/imageviewer/style/Decoration.kt

@ -1,38 +0,0 @@
package example.imageviewer.style
import androidx.compose.runtime.Composable
import androidx.compose.ui.res.painterResource
import example.imageviewer.common.R
@Composable
fun icEmpty() = painterResource(R.drawable.empty)
@Composable
fun icBack() = painterResource(R.drawable.back)
@Composable
fun icRefresh() = painterResource(R.drawable.refresh)
@Composable
fun icDots() = painterResource(R.drawable.dots)
@Composable
fun icFilterGrayscaleOn() = painterResource(R.drawable.grayscale_on)
@Composable
fun icFilterGrayscaleOff() = painterResource(R.drawable.grayscale_off)
@Composable
fun icFilterPixelOn() = painterResource(R.drawable.pixel_on)
@Composable
fun icFilterPixelOff() = painterResource(R.drawable.pixel_off)
@Composable
fun icFilterBlurOn() = painterResource(R.drawable.blur_on)
@Composable
fun icFilterBlurOff() = painterResource(R.drawable.blur_off)
@Composable
fun icFilterUnknown() = painterResource(R.drawable.filter_unknown)

52
experimental/examples/imageviewer/common/src/androidMain/kotlin/example/imageviewer/utils/Caching.kt

@ -1,52 +0,0 @@
package example.imageviewer.utils
import android.content.Context
import android.graphics.*
import example.imageviewer.model.Picture
import java.io.File
import java.io.BufferedWriter
import java.io.OutputStreamWriter
import java.io.FileOutputStream
import java.io.IOException
import java.nio.charset.StandardCharsets
val cacheImagePostfix = "info"
fun cacheImage(path: String, picture: Picture) {
try {
FileOutputStream(path).use { out ->
picture.image.compress(Bitmap.CompressFormat.PNG, 100, out)
}
val bw =
BufferedWriter(
OutputStreamWriter(
FileOutputStream(path + cacheImagePostfix), StandardCharsets.UTF_8
)
)
bw.write(picture.source)
bw.write("\r\n${picture.width}")
bw.write("\r\n${picture.height}")
bw.close()
} catch (e: IOException) {
e.printStackTrace()
}
}
fun clearCache(context: Context) {
val directory = File(context.cacheDir.absolutePath)
val files: Array<File>? = directory.listFiles()
if (files != null) {
for (file in files) {
if (file.isDirectory)
continue
file.delete()
}
}
}

9
experimental/examples/imageviewer/common/src/androidMain/kotlin/example/imageviewer/utils/Coroutines.kt

@ -1,9 +0,0 @@
package example.imageviewer.utils
import kotlinx.coroutines.CoroutineScope
import kotlin.coroutines.CoroutineContext
actual fun <T> runBlocking(
context: CoroutineContext,
block: suspend CoroutineScope.() -> T
): T = kotlinx.coroutines.runBlocking(context, block)

195
experimental/examples/imageviewer/common/src/androidMain/kotlin/example/imageviewer/utils/GraphicsMath.kt

@ -1,195 +0,0 @@
package example.imageviewer.utils
import android.content.Context
import android.content.res.Resources
import android.graphics.*
import android.renderscript.Allocation
import android.renderscript.Element
import android.renderscript.RenderScript
import android.renderscript.ScriptIntrinsicBlur
import androidx.compose.ui.layout.ContentScale
import kotlin.math.pow
import kotlin.math.roundToInt
import example.imageviewer.view.DragHandler
fun scaleBitmapAspectRatio(
bitmap: Bitmap,
width: Int,
height: Int,
filter: Boolean = false
): Bitmap {
val boundW: Float = width.toFloat()
val boundH: Float = height.toFloat()
val ratioX: Float = boundW / bitmap.width
val ratioY: Float = boundH / bitmap.height
val ratio: Float = if (ratioX < ratioY) ratioX else ratioY
val resultH = (bitmap.height * ratio).toInt()
val resultW = (bitmap.width * ratio).toInt()
return Bitmap.createScaledBitmap(bitmap, resultW, resultH, filter)
}
fun getDisplayBounds(bitmap: Bitmap): Rect {
val boundW: Float = displayWidth().toFloat()
val boundH: Float = displayHeight().toFloat()
val ratioX: Float = bitmap.width / boundW
val ratioY: Float = bitmap.height / boundH
val ratio: Float = if (ratioX > ratioY) ratioX else ratioY
val resultW = (boundW * ratio)
val resultH = (boundH * ratio)
return Rect(0, 0, resultW.toInt(), resultH.toInt())
}
fun applyGrayScaleFilter(bitmap: Bitmap): Bitmap {
val result: Bitmap = bitmap.copy(Bitmap.Config.ARGB_8888, true)
val canvas = Canvas(result)
val colorMatrix = ColorMatrix()
colorMatrix.setSaturation(0f)
val paint = Paint()
paint.colorFilter = ColorMatrixColorFilter(colorMatrix)
canvas.drawBitmap(result, 0f, 0f, paint)
return result
}
fun applyPixelFilter(bitmap: Bitmap): Bitmap {
var result: Bitmap = bitmap.copy(Bitmap.Config.ARGB_8888, true)
val w: Int = bitmap.width
val h: Int = bitmap.height
result = scaleBitmapAspectRatio(result, w / 20, h / 20)
result = scaleBitmapAspectRatio(result, w, h)
return result
}
fun applyBlurFilter(bitmap: Bitmap, context: Context): Bitmap {
val result: Bitmap = bitmap.copy(Bitmap.Config.ARGB_8888, true)
val renderScript: RenderScript = RenderScript.create(context)
val tmpIn: Allocation = Allocation.createFromBitmap(renderScript, bitmap)
val tmpOut: Allocation = Allocation.createFromBitmap(renderScript, result)
val theIntrinsic: ScriptIntrinsicBlur =
ScriptIntrinsicBlur.create(renderScript, Element.U8_4(renderScript))
theIntrinsic.setRadius(25f)
theIntrinsic.setInput(tmpIn)
theIntrinsic.forEach(tmpOut)
tmpOut.copyTo(result)
return result
}
fun adjustImageScale(bitmap: Bitmap): ContentScale {
val bitmapRatio = (10 * bitmap.width.toFloat() / bitmap.height).toInt()
val displayRatio = (10 * displayWidth().toFloat() / displayHeight()).toInt()
if (displayRatio > bitmapRatio) {
return ContentScale.FillHeight
}
return ContentScale.FillWidth
}
fun toPx(dp: Int): Int {
return (dp * Resources.getSystem().displayMetrics.density).toInt()
}
fun toDp(px: Int): Int {
return (px / Resources.getSystem().displayMetrics.density).toInt()
}
fun displayWidth(): Int {
return Resources.getSystem().displayMetrics.widthPixels
}
fun displayHeight(): Int {
return Resources.getSystem().displayMetrics.heightPixels
}
fun cropBitmapByScale(bitmap: Bitmap, scale: Float, drag: DragHandler): Bitmap {
val crop = cropBitmapByBounds(
bitmap,
getDisplayBounds(bitmap),
scale,
drag
)
return Bitmap.createBitmap(
bitmap,
crop.left,
crop.top,
crop.right - crop.left,
crop.bottom - crop.top
)
}
fun cropBitmapByBounds(
bitmap: Bitmap,
bounds: Rect,
scaleFactor: Float,
drag: DragHandler
): Rect {
if (scaleFactor <= 1f)
return Rect(0, 0, bitmap.width, bitmap.height)
var scale = scaleFactor.toDouble().pow(1.4)
var boundW = (bounds.width() / scale).roundToInt()
var boundH = (bounds.height() / scale).roundToInt()
scale *= displayWidth() / bounds.width().toDouble()
val offsetX = drag.getAmount().x / scale
val offsetY = drag.getAmount().y / scale
if (boundW > bitmap.width) {
boundW = bitmap.width
}
if (boundH > bitmap.height) {
boundH = bitmap.height
}
val invisibleW = bitmap.width - boundW
var leftOffset = (invisibleW / 2.0 - offsetX).roundToInt().toFloat()
if (leftOffset > invisibleW) {
leftOffset = invisibleW.toFloat()
drag.getAmount().x = -((invisibleW / 2.0) * scale).roundToInt().toFloat()
}
if (leftOffset < 0) {
drag.getAmount().x = ((invisibleW / 2.0) * scale).roundToInt().toFloat()
leftOffset = 0f
}
val invisibleH = bitmap.height - boundH
var topOffset = (invisibleH / 2 - offsetY).roundToInt().toFloat()
if (topOffset > invisibleH) {
topOffset = invisibleH.toFloat()
drag.getAmount().y = -((invisibleH / 2.0) * scale).roundToInt().toFloat()
}
if (topOffset < 0) {
drag.getAmount().y = ((invisibleH / 2.0) * scale).roundToInt().toFloat()
topOffset = 0f
}
return Rect(
leftOffset.toInt(),
topOffset.toInt(),
(leftOffset + boundW).toInt(),
(topOffset + boundH).toInt()
)
}

40
experimental/examples/imageviewer/common/src/androidMain/kotlin/example/imageviewer/view/AppUI.kt

@ -1,40 +0,0 @@
package example.imageviewer.view
import android.content.Context
import android.widget.Toast
import androidx.compose.runtime.Composable
import androidx.compose.ui.Modifier
import androidx.compose.foundation.layout.fillMaxSize
import androidx.compose.material.Surface
import androidx.compose.runtime.MutableState
import androidx.compose.runtime.mutableStateOf
import example.imageviewer.model.AppState
import example.imageviewer.model.ScreenType
import example.imageviewer.model.ContentState
import example.imageviewer.style.Gray
@Composable
fun AppUI(content: ContentState) {
Surface(
modifier = Modifier.fillMaxSize(),
color = Gray
) {
when (AppState.screenState()) {
ScreenType.MainScreen -> {
MainScreen(content)
}
ScreenType.FullscreenImage -> {
FullscreenImage(content)
}
}
}
}
fun showPopUpMessage(text: String, context: Context) {
Toast.makeText(
context,
text,
Toast.LENGTH_SHORT
).show()
}

197
experimental/examples/imageviewer/common/src/androidMain/kotlin/example/imageviewer/view/FullscreenImage.kt

@ -1,197 +0,0 @@
package example.imageviewer.view
import android.graphics.Bitmap
import android.graphics.Rect
import androidx.compose.foundation.Image
import androidx.compose.foundation.background
import androidx.compose.foundation.rememberScrollState
import androidx.compose.foundation.horizontalScroll
import androidx.compose.foundation.layout.Box
import androidx.compose.foundation.layout.Column
import androidx.compose.foundation.layout.Row
import androidx.compose.foundation.layout.Spacer
import androidx.compose.foundation.layout.fillMaxSize
import androidx.compose.foundation.layout.padding
import androidx.compose.foundation.layout.height
import androidx.compose.foundation.layout.size
import androidx.compose.foundation.layout.width
import androidx.compose.foundation.shape.CircleShape
import androidx.compose.material.CircularProgressIndicator
import androidx.compose.material.MaterialTheme
import androidx.compose.material.Surface
import androidx.compose.material.Text
import androidx.compose.runtime.Composable
import androidx.compose.runtime.remember
import androidx.compose.ui.Alignment
import androidx.compose.ui.Modifier
import androidx.compose.ui.draw.clip
import androidx.compose.ui.graphics.Color
import androidx.compose.ui.graphics.asImageBitmap
import androidx.compose.ui.graphics.painter.Painter
import androidx.compose.ui.graphics.ImageBitmap
import androidx.compose.ui.unit.dp
import example.imageviewer.core.FilterType
import example.imageviewer.model.AppState
import example.imageviewer.model.ContentState
import example.imageviewer.model.ScreenType
import example.imageviewer.style.DarkGray
import example.imageviewer.style.DarkGreen
import example.imageviewer.style.Foreground
import example.imageviewer.style.MiniatureColor
import example.imageviewer.style.Transparent
import example.imageviewer.style.icBack
import example.imageviewer.style.icFilterBlurOff
import example.imageviewer.style.icFilterBlurOn
import example.imageviewer.style.icFilterGrayscaleOff
import example.imageviewer.style.icFilterGrayscaleOn
import example.imageviewer.style.icFilterPixelOff
import example.imageviewer.style.icFilterPixelOn
import example.imageviewer.utils.adjustImageScale
import example.imageviewer.utils.cropBitmapByScale
import example.imageviewer.utils.displayWidth
import example.imageviewer.utils.getDisplayBounds
import kotlin.math.abs
import kotlin.math.pow
import kotlin.math.roundToInt
@Composable
fun FullscreenImage(
content: ContentState
) {
Column {
ToolBar(content.getSelectedImageName(), content)
Image(content)
}
if (!content.isContentReady()) {
LoadingScreen()
}
}
@Composable
fun ToolBar(
text: String,
content: ContentState
) {
val scrollState = rememberScrollState()
Surface(color = MiniatureColor, modifier = Modifier.height(44.dp)) {
Row(modifier = Modifier.padding(end = 30.dp)) {
Surface(
color = Transparent,
modifier = Modifier.padding(start = 20.dp).align(Alignment.CenterVertically),
shape = CircleShape
) {
Clickable(
onClick = {
if (content.isContentReady()) {
content.restoreMainImage()
AppState.screenState(ScreenType.MainScreen)
}
}) {
Image(
icBack(),
contentDescription = null,
modifier = Modifier.size(38.dp)
)
}
}
Text(
text,
color = Foreground,
maxLines = 1,
modifier = Modifier.padding(start = 30.dp).weight(1f)
.align(Alignment.CenterVertically),
style = MaterialTheme.typography.body1
)
Surface(
color = Color(255, 255, 255, 40),
modifier = Modifier.size(154.dp, 38.dp)
.align(Alignment.CenterVertically),
shape = CircleShape
) {
Row(Modifier.horizontalScroll(scrollState)) {
for (type in FilterType.values()) {
FilterButton(content, type)
}
}
}
}
}
}
@Composable
fun FilterButton(
content: ContentState,
type: FilterType,
modifier: Modifier = Modifier.size(38.dp)
) {
Box(
modifier = Modifier.background(color = Transparent).clip(CircleShape)
) {
Clickable(
onClick = { content.toggleFilter(type) }
) {
Image(
getFilterImage(type = type, content = content),
contentDescription = null,
modifier
)
}
}
Spacer(Modifier.width(20.dp))
}
@Composable
fun getFilterImage(type: FilterType, content: ContentState): Painter {
return when (type) {
FilterType.GrayScale -> if (content.isFilterEnabled(type)) icFilterGrayscaleOn() else icFilterGrayscaleOff()
FilterType.Pixel -> if (content.isFilterEnabled(type)) icFilterPixelOn() else icFilterPixelOff()
FilterType.Blur -> if (content.isFilterEnabled(type)) icFilterBlurOn() else icFilterBlurOff()
}
}
@Composable
fun Image(content: ContentState) {
val drag = remember { DragHandler() }
val scale = remember { ScaleHandler() }
Surface(
color = DarkGray,
modifier = Modifier.fillMaxSize()
) {
Draggable(dragHandler = drag, modifier = Modifier.fillMaxSize()) {
Scalable(onScale = scale, modifier = Modifier.fillMaxSize()) {
val bitmap = imageByGesture(content, scale, drag)
Image(
bitmap = bitmap.asImageBitmap(),
contentDescription = null,
contentScale = adjustImageScale(bitmap)
)
}
}
}
}
@Composable
fun imageByGesture(
content: ContentState,
scale: ScaleHandler,
drag: DragHandler
): Bitmap {
val bitmap = cropBitmapByScale(content.getSelectedImage(), scale.factor.value, drag)
if (scale.factor.value > 1f)
return bitmap
if (abs(drag.getDistance().x) > displayWidth() / 10) {
if (drag.getDistance().x < 0) {
content.swipeNext()
} else {
content.swipePrevious()
}
drag.cancel()
}
return bitmap
}

218
experimental/examples/imageviewer/common/src/androidMain/kotlin/example/imageviewer/view/MainScreen.kt

@ -1,218 +0,0 @@
package example.imageviewer.view
import android.content.res.Configuration
import androidx.compose.foundation.rememberScrollState
import androidx.compose.foundation.verticalScroll
import androidx.compose.foundation.Image
import androidx.compose.foundation.clickable
import androidx.compose.foundation.layout.Box
import androidx.compose.foundation.layout.Column
import androidx.compose.foundation.layout.Row
import androidx.compose.foundation.layout.Spacer
import androidx.compose.foundation.layout.fillMaxWidth
import androidx.compose.foundation.layout.height
import androidx.compose.foundation.layout.offset
import androidx.compose.foundation.layout.padding
import androidx.compose.foundation.layout.height
import androidx.compose.foundation.layout.size
import androidx.compose.foundation.layout.width
import androidx.compose.foundation.shape.CircleShape
import androidx.compose.material.Card
import androidx.compose.material.CircularProgressIndicator
import androidx.compose.material.Divider
import androidx.compose.material.MaterialTheme
import androidx.compose.material.Surface
import androidx.compose.material.Text
import androidx.compose.material.TopAppBar
import androidx.compose.runtime.Composable
import androidx.compose.ui.Alignment
import androidx.compose.ui.Modifier
import androidx.compose.ui.graphics.RectangleShape
import androidx.compose.ui.graphics.asImageBitmap
import androidx.compose.ui.graphics.painter.BitmapPainter
import androidx.compose.ui.layout.ContentScale
import androidx.compose.ui.unit.dp
import example.imageviewer.common.R
import example.imageviewer.model.AppState
import example.imageviewer.model.ContentState
import example.imageviewer.model.Picture
import example.imageviewer.model.ScreenType
import example.imageviewer.style.DarkGray
import example.imageviewer.style.DarkGreen
import example.imageviewer.style.Foreground
import example.imageviewer.style.LightGray
import example.imageviewer.style.MiniatureColor
import example.imageviewer.style.Transparent
import example.imageviewer.style.icDots
import example.imageviewer.style.icEmpty
import example.imageviewer.style.icRefresh
@Composable
fun MainScreen(content: ContentState) {
Column {
TopContent(content)
ScrollableArea(content)
}
if (!content.isContentReady()) {
LoadingScreen(content.getString(R.string.loading))
}
}
@Composable
fun TopContent(content: ContentState) {
TitleBar(text = content.getString(R.string.app_name), content = content)
if (content.getOrientation() == Configuration.ORIENTATION_PORTRAIT) {
PreviewImage(content)
Spacer(modifier = Modifier.height(10.dp))
Divider()
}
Spacer(modifier = Modifier.height(5.dp))
}
@Composable
fun TitleBar(text: String, content: ContentState) {
TopAppBar(
backgroundColor = DarkGreen,
title = {
Row(Modifier.height(50.dp)) {
Text(
text,
color = Foreground,
modifier = Modifier.weight(1f).align(Alignment.CenterVertically)
)
Surface(
color = Transparent,
modifier = Modifier.padding(end = 20.dp).align(Alignment.CenterVertically),
shape = CircleShape
) {
Clickable(
onClick = {
if (content.isContentReady()) {
content.refresh()
}
}
) {
Image(
icRefresh(),
contentDescription = null,
modifier = Modifier.size(35.dp)
)
}
}
}
})
}
@Composable
fun PreviewImage(content: ContentState) {
Clickable(onClick = {
AppState.screenState(ScreenType.FullscreenImage)
}) {
Card(
backgroundColor = DarkGray,
modifier = Modifier.height(250.dp),
shape = RectangleShape,
elevation = 1.dp
) {
Image(
if (content.isMainImageEmpty()) {
icEmpty()
} else {
BitmapPainter(content.getSelectedImage().asImageBitmap())
},
contentDescription = null,
modifier = Modifier
.fillMaxWidth().padding(start = 1.dp, top = 1.dp, end = 1.dp, bottom = 5.dp),
contentScale = ContentScale.Fit
)
}
}
}
@Composable
fun Miniature(
picture: Picture,
content: ContentState
) {
Card(
backgroundColor = MiniatureColor,
modifier = Modifier.padding(start = 10.dp, end = 10.dp).height(70.dp)
.fillMaxWidth()
.clickable {
content.setMainImage(picture)
},
shape = RectangleShape,
elevation = 2.dp
) {
Row(modifier = Modifier.padding(end = 30.dp)) {
Clickable(
onClick = {
content.fullscreen(picture)
}
) {
Image(
picture.image.asImageBitmap(),
contentDescription = null,
modifier = Modifier.height(70.dp)
.width(90.dp)
.padding(start = 1.dp, top = 1.dp, end = 1.dp, bottom = 1.dp),
contentScale = ContentScale.Crop
)
}
Text(
text = picture.name,
color = Foreground,
modifier = Modifier.weight(1f).align(Alignment.CenterVertically).padding(start = 16.dp),
style = MaterialTheme.typography.body1
)
Clickable(
modifier = Modifier.height(70.dp)
.width(30.dp),
onClick = {
showPopUpMessage(
"${content.getString(R.string.picture)} " +
"${picture.name} \n" +
"${content.getString(R.string.size)} " +
"${picture.width}x${picture.height} " +
"${content.getString(R.string.pixels)}",
content.getContext()
)
}
) {
Image(
icDots(),
contentDescription = null,
modifier = Modifier.height(70.dp)
.width(30.dp)
.padding(start = 1.dp, top = 25.dp, end = 1.dp, bottom = 25.dp),
contentScale = ContentScale.FillHeight
)
}
}
}
}
@Composable
fun ScrollableArea(content: ContentState) {
var index = 1
val scrollState = rememberScrollState()
Column(Modifier.verticalScroll(scrollState)) {
for (picture in content.getMiniatures()) {
Miniature(
picture = picture,
content = content
)
Spacer(modifier = Modifier.height(5.dp))
index++
}
}
}
@Composable
fun Divider() {
Divider(
color = LightGray,
modifier = Modifier.padding(start = 10.dp, end = 10.dp)
)
}

BIN
experimental/examples/imageviewer/common/src/androidMain/res/drawable/filter_unknown.png

Binary file not shown.

Before

Width:  |  Height:  |  Size: 11 KiB

18
experimental/examples/imageviewer/common/src/commonMain/kotlin/example/imageviewer/core/EventLocker.kt

@ -1,18 +0,0 @@
package example.imageviewer.core
class EventLocker {
private var value: Boolean = false
fun lock() {
value = false
}
fun unlock() {
value = true
}
fun isLocked(): Boolean {
return value
}
}

5
experimental/examples/imageviewer/common/src/commonMain/kotlin/example/imageviewer/core/Repository.kt

@ -1,5 +0,0 @@
package example.imageviewer.core
interface Repository<T> {
fun get() : T
}

33
experimental/examples/imageviewer/common/src/commonMain/kotlin/example/imageviewer/model/ImageRepository.kt

@ -1,33 +0,0 @@
// READ ME FIRST!
//
// Code in this file is shared between the Android and Desktop JVM targets.
// Kotlin's hierarchical multiplatform projects currently
// don't support sharing code depending on JVM declarations.
//
// You can follow the progress for HMPP JVM & Android intermediate source sets here:
// https://youtrack.jetbrains.com/issue/KT-42466
//
// The workaround used here to access JVM libraries causes IntelliJ IDEA to not
// resolve symbols in this file properly.
//
// Resolution errors in your IDE do not indicate a problem with your setup.
package example.imageviewer.model
import example.imageviewer.core.Repository
import example.imageviewer.utils.ktorHttpClient
import example.imageviewer.utils.runBlocking
import io.ktor.client.request.*
class ImageRepository(
private val httpsURL: String
) : Repository<MutableList<String>> {
override fun get(): MutableList<String> {
return runBlocking {
val content = ktorHttpClient.get<String>(httpsURL)
content.lines().toMutableList()
}
}
}

41
experimental/examples/imageviewer/common/src/commonMain/kotlin/example/imageviewer/model/Miniatures.kt

@ -1,41 +0,0 @@
// READ ME FIRST!
//
// Code in this file is shared between the Android and Desktop JVM targets.
// Kotlin's hierarchical multiplatform projects currently
// don't support sharing code depending on JVM declarations.
//
// You can follow the progress for HMPP JVM & Android intermediate source sets here:
// https://youtrack.jetbrains.com/issue/KT-42466
//
// The workaround used here to access JVM libraries causes IntelliJ IDEA to not
// resolve symbols in this file properly.
//
// Resolution errors in your IDE do not indicate a problem with your setup.
package example.imageviewer.model
expect class Picture
class Miniatures(
private var list: List<Picture> = emptyList()
) {
fun get(index: Int): Picture {
return list[index]
}
fun getMiniatures(): List<Picture> {
return list.toList()
}
fun setMiniatures(list: List<Picture>) {
this.list = list.toList()
}
fun size(): Int {
return list.size
}
fun clear() {
list = emptyList()
}
}

23
experimental/examples/imageviewer/common/src/commonMain/kotlin/example/imageviewer/model/ScreenType.kt

@ -1,23 +0,0 @@
package example.imageviewer.model
import androidx.compose.runtime.MutableState
import androidx.compose.runtime.mutableStateOf
enum class ScreenType {
MainScreen, FullscreenImage
}
object AppState {
private var screen: MutableState<ScreenType>
init {
screen = mutableStateOf(ScreenType.MainScreen)
}
fun screenState() : ScreenType {
return screen.value
}
fun screenState(state: ScreenType) {
screen.value = state
}
}

16
experimental/examples/imageviewer/common/src/commonMain/kotlin/example/imageviewer/style/Palette.kt

@ -1,16 +0,0 @@
package example.imageviewer.style
import androidx.compose.ui.graphics.Color
val DarkGreen = Color(16, 139, 102)
val Gray = Color.DarkGray
val LightGray = Color(100, 100, 100)
val DarkGray = Color(32, 32, 32)
val PreviewImageAreaHoverColor = Color(45, 45, 45)
val ToastBackground = Color(23, 23, 23)
val MiniatureColor = Color(50, 50, 50)
val MiniatureHoverColor = Color(55, 55, 55)
val Foreground = Color(210, 210, 210)
val TranslucentBlack = Color(0, 0, 0, 60)
val TranslucentWhite = Color(255, 255, 255, 20)
val Transparent = Color.Transparent

7
experimental/examples/imageviewer/common/src/commonMain/kotlin/example/imageviewer/utils/Coroutines.kt

@ -1,7 +0,0 @@
package example.imageviewer.utils
import kotlinx.coroutines.CoroutineScope
import kotlin.coroutines.CoroutineContext
import kotlin.coroutines.EmptyCoroutineContext
expect fun <T> runBlocking(context: CoroutineContext = EmptyCoroutineContext, block: suspend CoroutineScope.() -> T): T

37
experimental/examples/imageviewer/common/src/commonMain/kotlin/example/imageviewer/utils/Network.kt

@ -1,37 +0,0 @@
// READ ME FIRST!
//
// Code in this file is shared between the Android and Desktop JVM targets.
// Kotlin's hierarchical multiplatform projects currently
// don't support sharing code depending on JVM declarations.
//
// You can follow the progress for HMPP JVM & Android intermediate source sets here:
// https://youtrack.jetbrains.com/issue/KT-42466
//
// The workaround used here to access JVM libraries causes IntelliJ IDEA to not
// resolve symbols in this file properly.
//
// Resolution errors in your IDE do not indicate a problem with your setup.
package example.imageviewer.utils
import io.ktor.client.*
import io.ktor.client.request.*
import kotlinx.coroutines.Deferred
import kotlinx.coroutines.GlobalScope
import kotlinx.coroutines.async
//import java.net.InetAddress
fun isInternetAvailable(): Boolean {
return runBlocking {
try {
ktorHttpClient.head<String>("http://google.com")
true
} catch (e: Exception) {
println(e.message)
false
}
}
}
val ktorHttpClient = HttpClient {}

21
experimental/examples/imageviewer/common/src/commonMain/kotlin/example/imageviewer/view/Clickable.kt

@ -1,21 +0,0 @@
package example.imageviewer.view
import androidx.compose.runtime.Composable
import androidx.compose.foundation.clickable
import androidx.compose.foundation.layout.Box
import androidx.compose.ui.Modifier
@Composable
fun Clickable(
modifier: Modifier = Modifier,
onClick: (() -> Unit)? = null,
children: @Composable () -> Unit = { }
) {
Box(
modifier = modifier.clickable {
onClick?.invoke()
}
) {
children()
}
}

88
experimental/examples/imageviewer/common/src/commonMain/kotlin/example/imageviewer/view/Draggable.kt

@ -1,88 +0,0 @@
package example.imageviewer.view
import androidx.compose.foundation.gestures.detectDragGestures
import androidx.compose.material.Surface
import androidx.compose.runtime.Composable
import androidx.compose.runtime.mutableStateOf
import androidx.compose.ui.input.pointer.pointerInput
import androidx.compose.ui.Modifier
import androidx.compose.ui.geometry.Offset
import example.imageviewer.core.EventLocker
import example.imageviewer.style.Transparent
@Composable
fun Draggable(
dragHandler: DragHandler,
modifier: Modifier = Modifier,
onUpdate: (() -> Unit)? = null,
children: @Composable() () -> Unit
) {
Surface(
color = Transparent,
modifier = modifier.pointerInput(Unit) {
detectDragGestures(
onDragStart = { dragHandler.reset() },
onDragEnd = { dragHandler.reset() },
onDragCancel = { dragHandler.cancel() },
) { change, dragAmount ->
dragHandler.drag(dragAmount)
onUpdate?.invoke()
change.consume()
}
}
) {
children()
}
}
class DragHandler {
private val amount = mutableStateOf(Point(0f, 0f))
private val distance = mutableStateOf(Point(0f, 0f))
private val locker: EventLocker = EventLocker()
fun getAmount(): Point {
return amount.value
}
fun getDistance(): Point {
return distance.value
}
fun reset() {
distance.value = Point(Offset.Zero)
locker.unlock()
}
fun cancel() {
distance.value = Point(Offset.Zero)
locker.lock()
}
fun drag(dragDistance: Offset) {
if (locker.isLocked()) {
val dx = dragDistance.x
val dy = dragDistance.y
distance.value = Point(distance.value.x + dx, distance.value.y + dy)
amount.value = Point(amount.value.x + dx, amount.value.y + dy)
}
}
}
class Point {
var x: Float = 0f
var y: Float = 0f
constructor(x: Float, y: Float) {
this.x = x
this.y = y
}
constructor(point: Offset) {
this.x = point.x
this.y = point.y
}
fun setAttr(x: Float, y: Float) {
this.x = x
this.y = y
}
}

47
experimental/examples/imageviewer/common/src/commonMain/kotlin/example/imageviewer/view/Scalable.kt

@ -1,47 +0,0 @@
package example.imageviewer.view
import androidx.compose.foundation.gestures.detectTapGestures
import androidx.compose.foundation.gestures.detectTransformGestures
import androidx.compose.material.Surface
import androidx.compose.runtime.Composable
import androidx.compose.runtime.mutableStateOf
import androidx.compose.ui.Modifier
import androidx.compose.ui.input.pointer.pointerInput
import example.imageviewer.style.Transparent
@Composable
fun Scalable(
onScale: ScaleHandler,
modifier: Modifier = Modifier,
children: @Composable() () -> Unit
) {
Surface(
color = Transparent,
modifier = modifier.pointerInput(Unit) {
detectTapGestures(onDoubleTap = { onScale.reset() })
detectTransformGestures { _, _, zoom, _ ->
onScale.onScale(zoom)
}
},
) {
children()
}
}
class ScaleHandler(private val maxFactor: Float = 5f, private val minFactor: Float = 1f) {
val factor = mutableStateOf(1f)
fun reset() {
if (factor.value > minFactor)
factor.value = minFactor
}
fun onScale(scaleFactor: Float): Float {
factor.value += scaleFactor - 1f
if (maxFactor < factor.value) factor.value = maxFactor
if (minFactor > factor.value) factor.value = minFactor
return scaleFactor
}
}

27
experimental/examples/imageviewer/common/src/commonMain/kotlin/example/imageviewer/view/SplashUI.kt

@ -1,27 +0,0 @@
package example.imageviewer.view
import androidx.compose.foundation.background
import androidx.compose.foundation.layout.Box
import androidx.compose.foundation.layout.fillMaxSize
import androidx.compose.material.Text
import androidx.compose.runtime.Composable
import androidx.compose.ui.Alignment
import androidx.compose.ui.Modifier
import androidx.compose.ui.graphics.Color
import androidx.compose.ui.text.font.FontWeight
import androidx.compose.ui.unit.sp
import example.imageviewer.style.DarkGray
@Composable
fun SplashUI() {
Box(Modifier.fillMaxSize().background(DarkGray)) {
Text(
// TODO implement common resources
"Image Viewer",
Modifier.align(Alignment.Center),
color = Color.White,
fontWeight = FontWeight.Bold,
fontSize = 100.sp
)
}
}

7
experimental/examples/imageviewer/common/src/desktopMain/kotlin/example/imageviewer/core/BitmapFilter.kt

@ -1,7 +0,0 @@
package example.imageviewer.core
import java.awt.image.BufferedImage
interface BitmapFilter {
fun apply(bitmap: BufferedImage) : BufferedImage
}

362
experimental/examples/imageviewer/common/src/desktopMain/kotlin/example/imageviewer/model/DesktopContentState.kt

@ -1,362 +0,0 @@
package example.imageviewer.model
import androidx.compose.runtime.MutableState
import androidx.compose.runtime.mutableStateOf
import androidx.compose.ui.window.WindowState
import androidx.compose.ui.graphics.ImageBitmap
import androidx.compose.ui.graphics.toComposeImageBitmap
import example.imageviewer.ResString
import example.imageviewer.core.FilterType
import example.imageviewer.model.filtration.FiltersManager
import example.imageviewer.utils.cacheImagePath
import example.imageviewer.utils.clearCache
import example.imageviewer.utils.isInternetAvailable
import example.imageviewer.view.showPopUpMessage
import example.imageviewer.view.DragHandler
import example.imageviewer.view.ScaleHandler
import example.imageviewer.utils.cropBitmapByScale
import example.imageviewer.utils.toByteArray
import java.awt.image.BufferedImage
import java.io.File
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.launch
import kotlinx.coroutines.CoroutineScope
import org.jetbrains.skia.Image
object ContentState {
val drag = DragHandler()
val scale = ScaleHandler()
lateinit var windowState: WindowState
private lateinit var repository: ImageRepository
private lateinit var uriRepository: String
val scope = CoroutineScope(Dispatchers.IO)
fun applyContent(state: WindowState, uriRepository: String): ContentState {
windowState = state
if (this::uriRepository.isInitialized && this.uriRepository == uriRepository) {
return this
}
this.uriRepository = uriRepository
repository = ImageRepository(uriRepository)
isContentReady.value = false
initData()
return this
}
private val isAppReady = mutableStateOf(false)
fun isAppReady(): Boolean {
return isAppReady.value
}
private val isContentReady = mutableStateOf(false)
fun isContentReady(): Boolean {
return isContentReady.value
}
// drawable content
private val mainImage = mutableStateOf(BufferedImage(1, 1, BufferedImage.TYPE_INT_ARGB))
private val currentImageIndex = mutableStateOf(0)
private val miniatures = Miniatures()
fun getMiniatures(): List<Picture> {
return miniatures.getMiniatures()
}
fun getSelectedImage(): ImageBitmap {
return MainImageWrapper.mainImageAsImageBitmap.value
}
fun getSelectedImageName(): String {
return MainImageWrapper.getName()
}
// filters managing
private val appliedFilters = FiltersManager()
private val filterUIState: MutableMap<FilterType, MutableState<Boolean>> = LinkedHashMap()
private fun toggleFilterState(filter: FilterType) {
if (!filterUIState.containsKey(filter)) {
filterUIState[filter] = mutableStateOf(true)
} else {
val value = filterUIState[filter]!!.value
filterUIState[filter]!!.value = !value
}
}
fun toggleFilter(filter: FilterType) {
if (containsFilter(filter)) {
removeFilter(filter)
} else {
addFilter(filter)
}
toggleFilterState(filter)
var bitmap = MainImageWrapper.origin
if (bitmap != null) {
bitmap = appliedFilters.applyFilters(bitmap)
MainImageWrapper.setImage(bitmap)
mainImage.value = bitmap
updateMainImage()
}
}
private fun addFilter(filter: FilterType) {
appliedFilters.add(filter)
MainImageWrapper.addFilter(filter)
}
private fun removeFilter(filter: FilterType) {
appliedFilters.remove(filter)
MainImageWrapper.removeFilter(filter)
}
private fun containsFilter(type: FilterType): Boolean {
return appliedFilters.contains(type)
}
fun isFilterEnabled(type: FilterType): Boolean {
if (!filterUIState.containsKey(type)) {
filterUIState[type] = mutableStateOf(false)
}
return filterUIState[type]!!.value
}
private fun restoreFilters(): BufferedImage {
filterUIState.clear()
appliedFilters.clear()
return MainImageWrapper.restore()
}
fun restoreMainImage() {
mainImage.value = restoreFilters()
}
// application content initialization
private fun initData() {
if (isContentReady.value)
return
val directory = File(cacheImagePath)
if (!directory.exists()) {
directory.mkdir()
}
scope.launch(Dispatchers.IO) {
try {
if (isInternetAvailable()) {
val imageList = repository.get()
if (imageList.isEmpty()) {
showPopUpMessage(
ResString.repoInvalid
)
onContentReady()
} else {
val pictureList = loadImages(cacheImagePath, imageList)
if (pictureList.isEmpty()) {
showPopUpMessage(
ResString.repoEmpty
)
onContentReady()
} else {
val picture = loadFullImage(imageList[0])
miniatures.setMiniatures(pictureList)
if (isMainImageEmpty()) {
wrapPictureIntoMainImage(picture)
} else {
appliedFilters.add(MainImageWrapper.getFilters())
currentImageIndex.value = MainImageWrapper.getId()
}
onContentReady()
}
}
} else {
showPopUpMessage(
ResString.noInternet
)
onContentReady()
}
} catch (e: Exception) {
e.printStackTrace()
}
}
}
// preview/fullscreen image managing
fun isMainImageEmpty(): Boolean {
return MainImageWrapper.isEmpty()
}
fun fullscreen(picture: Picture) {
isContentReady.value = false
AppState.screenState(ScreenType.FullscreenImage)
setMainImage(picture)
}
fun setMainImage(picture: Picture) {
if (MainImageWrapper.getId() == picture.id) {
if (!isContentReady()) {
onContentReady()
}
return
}
isContentReady.value = false
scope.launch(Dispatchers.IO) {
scale.reset()
if (isInternetAvailable()) {
val fullSizePicture = loadFullImage(picture.source)
fullSizePicture.id = picture.id
wrapPictureIntoMainImage(fullSizePicture)
} else {
showPopUpMessage(
"${ResString.noInternet}\n${ResString.loadImageUnavailable}"
)
wrapPictureIntoMainImage(picture)
}
onContentReady()
}
}
private fun onContentReady() {
isContentReady.value = true
isAppReady.value = true
}
private fun wrapPictureIntoMainImage(picture: Picture) {
MainImageWrapper.wrapPicture(picture)
MainImageWrapper.saveOrigin()
mainImage.value = picture.image
currentImageIndex.value = picture.id
updateMainImage()
}
fun updateMainImage() {
MainImageWrapper.mainImageAsImageBitmap.value = Image.makeFromEncoded(
toByteArray(
cropBitmapByScale(
mainImage.value,
windowState.size,
scale.factor.value,
drag
)
)
).toComposeImageBitmap()
}
fun swipeNext() {
if (currentImageIndex.value == miniatures.size() - 1) {
showPopUpMessage(ResString.lastImage)
return
}
restoreFilters()
setMainImage(miniatures.get(++currentImageIndex.value))
}
fun swipePrevious() {
if (currentImageIndex.value == 0) {
showPopUpMessage(ResString.firstImage)
return
}
restoreFilters()
setMainImage(miniatures.get(--currentImageIndex.value))
}
fun refresh() {
scope.launch(Dispatchers.IO) {
if (isInternetAvailable()) {
clearCache()
MainImageWrapper.clear()
miniatures.clear()
isContentReady.value = false
initData()
} else {
showPopUpMessage(
"${ResString.noInternet}\n${ResString.refreshUnavailable}"
)
}
}
}
}
private object MainImageWrapper {
// origin image
var origin: BufferedImage? = null
private set
fun saveOrigin() {
origin = copy(picture.value.image)
}
fun restore(): BufferedImage {
if (origin != null) {
picture.value.image = copy(origin!!)
filtersSet.clear()
}
return copy(picture.value.image)
}
var mainImageAsImageBitmap = mutableStateOf(ImageBitmap(1, 1))
// picture adapter
private var picture = mutableStateOf(
Picture(image = BufferedImage(1, 1, BufferedImage.TYPE_INT_ARGB))
)
fun wrapPicture(picture: Picture) {
this.picture.value = picture
}
fun setImage(bitmap: BufferedImage) {
picture.value.image = bitmap
}
fun isEmpty(): Boolean {
return (picture.value.name == "")
}
fun clear() {
picture.value = Picture(image = BufferedImage(1, 1, BufferedImage.TYPE_INT_ARGB))
}
fun getName(): String {
return picture.value.name
}
fun getImage(): BufferedImage {
return picture.value.image
}
fun getId(): Int {
return picture.value.id
}
// applied filters
private var filtersSet: MutableSet<FilterType> = LinkedHashSet()
fun addFilter(filter: FilterType) {
filtersSet.add(filter)
}
fun removeFilter(filter: FilterType) {
filtersSet.remove(filter)
}
fun getFilters(): Set<FilterType> {
return filtersSet
}
private fun copy(bitmap: BufferedImage) : BufferedImage {
val result = BufferedImage(bitmap.width, bitmap.height, bitmap.type)
val graphics = result.createGraphics()
graphics.drawImage(bitmap, 0, 0, result.width, result.height, null)
return result
}
}

130
experimental/examples/imageviewer/common/src/desktopMain/kotlin/example/imageviewer/model/ImageHandler.kt

@ -1,130 +0,0 @@
package example.imageviewer.model
import java.awt.image.BufferedImage
import example.imageviewer.utils.cacheImage
import example.imageviewer.utils.cacheImagePostfix
import example.imageviewer.utils.scaleBitmapAspectRatio
import java.io.File
import java.io.FileInputStream
import java.io.InputStream
import java.io.InputStreamReader
import java.io.BufferedReader
import javax.imageio.ImageIO
import java.lang.Exception
import java.net.HttpURLConnection
import java.net.URL
import java.nio.charset.StandardCharsets
fun loadFullImage(source: String): Picture {
try {
val url = URL(source)
val connection: HttpURLConnection = url.openConnection() as HttpURLConnection
connection.connectTimeout = 5000
connection.connect()
val input: InputStream = connection.inputStream
val bitmap: BufferedImage? = ImageIO.read(input)
if (bitmap != null) {
return Picture(
source = source,
image = bitmap,
name = getNameURL(source),
width = bitmap.width,
height = bitmap.height
)
}
} catch (e: Exception) {
e.printStackTrace()
}
return Picture(image = BufferedImage(1, 1, BufferedImage.TYPE_INT_ARGB))
}
fun loadImages(cachePath: String, list: List<String>): MutableList<Picture> {
val result: MutableList<Picture> = ArrayList()
for (source in list) {
val name = getNameURL(source)
val path = cachePath + File.separator + name
if (File(path + "info").exists()) {
addCachedMiniature(filePath = path, outList = result)
} else {
addFreshMiniature(source = source, outList = result, path = cachePath)
}
result.last().id = result.size - 1
}
return result
}
private fun addFreshMiniature(
source: String,
outList: MutableList<Picture>,
path: String
) {
try {
val url = URL(source)
val connection: HttpURLConnection = url.openConnection() as HttpURLConnection
connection.connectTimeout = 5000
connection.connect()
val input: InputStream = connection.inputStream
val result: BufferedImage? = ImageIO.read(input)
if (result != null) {
val picture = Picture(
source,
getNameURL(source),
scaleBitmapAspectRatio(result, 200, 164),
result.width,
result.height
)
outList.add(picture)
cacheImage(path + getNameURL(source), picture)
}
} catch (e: Exception) {
e.printStackTrace()
}
}
private fun addCachedMiniature(
filePath: String,
outList: MutableList<Picture>
) {
try {
val read = BufferedReader(
InputStreamReader(
FileInputStream(filePath + cacheImagePostfix),
StandardCharsets.UTF_8
)
)
val source = read.readLine()
val width = read.readLine().toInt()
val height = read.readLine().toInt()
read.close()
val result: BufferedImage? = ImageIO.read(File(filePath))
if (result != null) {
val picture = Picture(
source,
getNameURL(source),
result,
width,
height
)
outList.add(picture)
}
} catch (e: Exception) {
e.printStackTrace()
}
}
private fun getNameURL(url: String): String {
return url.substring(url.lastIndexOf('/') + 1, url.length)
}

12
experimental/examples/imageviewer/common/src/desktopMain/kotlin/example/imageviewer/model/Picture.kt

@ -1,12 +0,0 @@
package example.imageviewer.model
import java.awt.image.BufferedImage
actual data class Picture(
var source: String = "",
var name: String = "",
var image: BufferedImage,
var width: Int = 0,
var height: Int = 0,
var id: Int = 0
)

12
experimental/examples/imageviewer/common/src/desktopMain/kotlin/example/imageviewer/model/filtration/BlurFilter.kt

@ -1,12 +0,0 @@
package example.imageviewer.model.filtration
import java.awt.image.BufferedImage
import example.imageviewer.core.BitmapFilter
import example.imageviewer.utils.applyBlurFilter
class BlurFilter : BitmapFilter {
override fun apply(bitmap: BufferedImage): BufferedImage {
return applyBlurFilter(bitmap)
}
}

12
experimental/examples/imageviewer/common/src/desktopMain/kotlin/example/imageviewer/model/filtration/EmptyFilter.kt

@ -1,12 +0,0 @@
package example.imageviewer.model.filtration
import java.awt.image.BufferedImage
import example.imageviewer.core.BitmapFilter
class EmptyFilter : BitmapFilter {
override fun apply(bitmap: BufferedImage): BufferedImage {
return bitmap
}
}

53
experimental/examples/imageviewer/common/src/desktopMain/kotlin/example/imageviewer/model/filtration/FiltersManager.kt

@ -1,53 +0,0 @@
package example.imageviewer.model.filtration
import java.awt.image.BufferedImage
import example.imageviewer.core.BitmapFilter
import example.imageviewer.core.FilterType
class FiltersManager {
private var filtersMap: MutableMap<FilterType, BitmapFilter> = LinkedHashMap()
fun clear() {
filtersMap = LinkedHashMap()
}
fun add(filters: Collection<FilterType>) {
for (filter in filters)
add(filter)
}
fun add(filter: FilterType) {
if (!filtersMap.containsKey(filter))
filtersMap[filter] = getFilter(filter)
}
fun remove(filter: FilterType) {
filtersMap.remove(filter)
}
fun contains(filter: FilterType): Boolean {
return filtersMap.contains(filter)
}
fun applyFilters(bitmap: BufferedImage): BufferedImage {
var result: BufferedImage = bitmap
for (filter in filtersMap) {
result = filter.value.apply(result)
}
return result
}
}
private fun getFilter(type: FilterType): BitmapFilter {
return when (type) {
FilterType.GrayScale -> GrayScaleFilter()
FilterType.Pixel -> PixelFilter()
FilterType.Blur -> BlurFilter()
}
}

12
experimental/examples/imageviewer/common/src/desktopMain/kotlin/example/imageviewer/model/filtration/GrayScaleFilter.kt

@ -1,12 +0,0 @@
package example.imageviewer.model.filtration
import java.awt.image.BufferedImage
import example.imageviewer.core.BitmapFilter
import example.imageviewer.utils.applyGrayScaleFilter
class GrayScaleFilter : BitmapFilter {
override fun apply(bitmap: BufferedImage) : BufferedImage {
return applyGrayScaleFilter(bitmap)
}
}

12
experimental/examples/imageviewer/common/src/desktopMain/kotlin/example/imageviewer/model/filtration/PixelFilter.kt

@ -1,12 +0,0 @@
package example.imageviewer.model.filtration
import java.awt.image.BufferedImage
import example.imageviewer.core.BitmapFilter
import example.imageviewer.utils.applyPixelFilter
class PixelFilter : BitmapFilter {
override fun apply(bitmap: BufferedImage): BufferedImage {
return applyPixelFilter(bitmap)
}
}

42
experimental/examples/imageviewer/common/src/desktopMain/kotlin/example/imageviewer/style/Decoration.kt

@ -1,42 +0,0 @@
package example.imageviewer.style
import androidx.compose.runtime.Composable
import androidx.compose.ui.res.painterResource
import java.awt.image.BufferedImage
import javax.imageio.ImageIO
@Composable
fun icEmpty() = painterResource("images/empty.png")
@Composable
fun icBack() = painterResource("images/back.png")
@Composable
fun icRefresh() = painterResource("images/refresh.png")
@Composable
fun icDots() = painterResource("images/dots.png")
@Composable
fun icFilterGrayscaleOn() = painterResource("images/grayscale_on.png")
@Composable
fun icFilterGrayscaleOff() = painterResource("images/grayscale_off.png")
@Composable
fun icFilterPixelOn() = painterResource("images/pixel_on.png")
@Composable
fun icFilterPixelOff() = painterResource("images/pixel_off.png")
@Composable
fun icFilterBlurOn() = painterResource("images/blur_on.png")
@Composable
fun icFilterBlurOff() = painterResource("images/blur_off.png")
@Composable
fun icFilterUnknown() = painterResource("images/filter_unknown.png")
@Composable
fun icAppRounded() = painterResource("images/ic_imageviewer_round.png")

53
experimental/examples/imageviewer/common/src/desktopMain/kotlin/example/imageviewer/utils/Caching.kt

@ -1,53 +0,0 @@
package example.imageviewer.utils
import java.awt.image.BufferedImage
import example.imageviewer.model.Picture
import javax.imageio.ImageIO
import java.io.File
import java.io.BufferedWriter
import java.io.OutputStreamWriter
import java.io.FileOutputStream
import java.io.IOException
import java.nio.charset.StandardCharsets
val cacheImagePostfix = "info"
val cacheImagePath = System.getProperty("user.home")!! +
File.separator + "Pictures/imageviewer" + File.separator
fun cacheImage(path: String, picture: Picture) {
try {
ImageIO.write(picture.image, "png", File(path))
val bw =
BufferedWriter(
OutputStreamWriter(
FileOutputStream(path + cacheImagePostfix),
StandardCharsets.UTF_8
)
)
bw.write(picture.source)
bw.write("\r\n${picture.width}")
bw.write("\r\n${picture.height}")
bw.close()
} catch (e: IOException) {
e.printStackTrace()
}
}
fun clearCache() {
val directory = File(cacheImagePath)
val files: Array<File>? = directory.listFiles()
if (files != null) {
for (file in files) {
if (file.isDirectory)
continue
file.delete()
}
}
}

9
experimental/examples/imageviewer/common/src/desktopMain/kotlin/example/imageviewer/utils/Coroutines.kt

@ -1,9 +0,0 @@
package example.imageviewer.utils
import kotlinx.coroutines.CoroutineScope
import kotlin.coroutines.CoroutineContext
actual fun <T> runBlocking(
context: CoroutineContext,
block: suspend CoroutineScope.() -> T
): T = kotlinx.coroutines.runBlocking(context, block)

206
experimental/examples/imageviewer/common/src/desktopMain/kotlin/example/imageviewer/utils/GraphicsMath.kt

@ -1,206 +0,0 @@
package example.imageviewer.utils
import androidx.compose.ui.unit.DpSize
import androidx.compose.ui.unit.dp
import java.awt.Dimension
import java.awt.Graphics2D
import java.awt.Rectangle
import java.awt.Toolkit
import java.awt.image.BufferedImage
import java.io.ByteArrayOutputStream
import java.io.File
import java.io.IOException
import javax.imageio.ImageIO
import java.awt.image.BufferedImageOp
import java.awt.image.ConvolveOp
import java.awt.image.Kernel
import kotlin.math.pow
import kotlin.math.roundToInt
import example.imageviewer.view.DragHandler
fun scaleBitmapAspectRatio(
bitmap: BufferedImage,
width: Int,
height: Int
): BufferedImage {
val boundW: Float = width.toFloat()
val boundH: Float = height.toFloat()
val ratioX: Float = boundW / bitmap.width
val ratioY: Float = boundH / bitmap.height
val ratio: Float = if (ratioX < ratioY) ratioX else ratioY
val resultH = (bitmap.height * ratio).toInt()
val resultW = (bitmap.width * ratio).toInt()
val result = BufferedImage(resultW, resultH, BufferedImage.TYPE_INT_ARGB)
val graphics = result.createGraphics()
graphics.drawImage(bitmap, 0, 0, resultW, resultH, null)
graphics.dispose()
return result
}
fun getDisplayBounds(bitmap: BufferedImage, windowSize: DpSize): Rectangle {
val boundW: Float = windowSize.width.value.toFloat()
val boundH: Float = windowSize.height.value.toFloat()
val ratioX: Float = bitmap.width / boundW
val ratioY: Float = bitmap.height / boundH
val ratio: Float = if (ratioX > ratioY) ratioX else ratioY
val resultW = (boundW * ratio)
val resultH = (boundH * ratio)
return Rectangle(0, 0, resultW.toInt(), resultH.toInt())
}
fun applyGrayScaleFilter(bitmap: BufferedImage): BufferedImage {
val result = BufferedImage(
bitmap.getWidth(),
bitmap.getHeight(),
BufferedImage.TYPE_BYTE_GRAY)
val graphics = result.getGraphics()
graphics.drawImage(bitmap, 0, 0, null)
graphics.dispose()
return result
}
fun applyPixelFilter(bitmap: BufferedImage): BufferedImage {
val w: Int = bitmap.width
val h: Int = bitmap.height
var result = scaleBitmapAspectRatio(bitmap, w / 20, h / 20)
result = scaleBitmapAspectRatio(result, w, h)
return result
}
fun applyBlurFilter(bitmap: BufferedImage): BufferedImage {
var result = BufferedImage(bitmap.getWidth(), bitmap.getHeight(), bitmap.type)
val graphics = result.getGraphics()
graphics.drawImage(bitmap, 0, 0, null)
graphics.dispose()
val radius = 11
val size = 11
val weight: Float = 1.0f / (size * size)
val matrix = FloatArray(size * size)
for (i in 0..matrix.size - 1) {
matrix[i] = weight
}
val kernel = Kernel(radius, size, matrix)
val op = ConvolveOp(kernel, ConvolveOp.EDGE_NO_OP, null)
result = op.filter(result, null)
return result.getSubimage(
radius,
radius,
result.width - radius * 2,
result.height - radius * 2
)
}
fun toByteArray(bitmap: BufferedImage) : ByteArray {
val baos = ByteArrayOutputStream()
ImageIO.write(bitmap, "png", baos)
return baos.toByteArray()
}
fun cropImage(bitmap: BufferedImage, crop: Rectangle) : BufferedImage {
return bitmap.getSubimage(crop.x, crop.y, crop.width, crop.height)
}
fun cropBitmapByScale(
bitmap: BufferedImage,
size: DpSize,
scale: Float,
drag: DragHandler
): BufferedImage {
val crop = cropBitmapByBounds(
bitmap,
getDisplayBounds(bitmap, size),
size,
scale,
drag
)
return cropImage(
bitmap,
Rectangle(crop.x, crop.y, crop.width - crop.x, crop.height - crop.y)
)
}
fun cropBitmapByBounds(
bitmap: BufferedImage,
bounds: Rectangle,
size: DpSize,
scaleFactor: Float,
drag: DragHandler
): Rectangle {
if (scaleFactor <= 1f) {
return Rectangle(0, 0, bitmap.width, bitmap.height)
}
var scale = scaleFactor.toDouble().pow(1.4)
var boundW = (bounds.width / scale).roundToInt()
var boundH = (bounds.height / scale).roundToInt()
scale *= size.width.value / bounds.width.toDouble()
val offsetX = drag.getAmount().x / scale
val offsetY = drag.getAmount().y / scale
if (boundW > bitmap.width) {
boundW = bitmap.width
}
if (boundH > bitmap.height) {
boundH = bitmap.height
}
val invisibleW = bitmap.width - boundW
var leftOffset = (invisibleW / 2.0 - offsetX).roundToInt()
if (leftOffset > invisibleW) {
leftOffset = invisibleW
drag.getAmount().x = -((invisibleW / 2.0) * scale).roundToInt().toFloat()
}
if (leftOffset < 0) {
drag.getAmount().x = ((invisibleW / 2.0) * scale).roundToInt().toFloat()
leftOffset = 0
}
val invisibleH = bitmap.height - boundH
var topOffset = (invisibleH / 2 - offsetY).roundToInt()
if (topOffset > invisibleH) {
topOffset = invisibleH
drag.getAmount().y = -((invisibleH / 2.0) * scale).roundToInt().toFloat()
}
if (topOffset < 0) {
drag.getAmount().y = ((invisibleH / 2.0) * scale).roundToInt().toFloat()
topOffset = 0
}
return Rectangle(leftOffset, topOffset, leftOffset + boundW, topOffset + boundH)
}
fun getPreferredWindowSize(desiredWidth: Int, desiredHeight: Int): DpSize {
val screenSize: Dimension = Toolkit.getDefaultToolkit().screenSize
val preferredWidth: Int = (screenSize.width * 0.8f).toInt()
val preferredHeight: Int = (screenSize.height * 0.8f).toInt()
val width: Int = if (desiredWidth < preferredWidth) desiredWidth else preferredWidth
val height: Int = if (desiredHeight < preferredHeight) desiredHeight else preferredHeight
return DpSize(width.dp, height.dp)
}

40
experimental/examples/imageviewer/common/src/desktopMain/kotlin/example/imageviewer/view/AppUI.kt

@ -1,40 +0,0 @@
package example.imageviewer.view
import androidx.compose.runtime.Composable
import androidx.compose.ui.Modifier
import androidx.compose.foundation.layout.fillMaxSize
import androidx.compose.material.Surface
import androidx.compose.runtime.MutableState
import androidx.compose.runtime.mutableStateOf
import example.imageviewer.model.AppState
import example.imageviewer.model.ScreenType
import example.imageviewer.model.ContentState
import example.imageviewer.style.Gray
private val message: MutableState<String> = mutableStateOf("")
private val state: MutableState<Boolean> = mutableStateOf(false)
@Composable
fun AppUI(content: ContentState) {
Surface(
modifier = Modifier.fillMaxSize(),
color = Gray
) {
when (AppState.screenState()) {
ScreenType.MainScreen -> {
MainScreen(content)
}
ScreenType.FullscreenImage -> {
FullscreenImage(content)
}
}
}
Toast(message.value, state)
}
fun showPopUpMessage(text: String) {
message.value = text
state.value = true
}

207
experimental/examples/imageviewer/common/src/desktopMain/kotlin/example/imageviewer/view/FullscreenImage.kt

@ -1,207 +0,0 @@
package example.imageviewer.view
import androidx.compose.foundation.*
import androidx.compose.foundation.layout.Box
import androidx.compose.foundation.layout.Column
import androidx.compose.foundation.layout.Row
import androidx.compose.foundation.layout.Spacer
import androidx.compose.foundation.layout.fillMaxSize
import androidx.compose.foundation.layout.padding
import androidx.compose.foundation.layout.height
import androidx.compose.foundation.layout.size
import androidx.compose.foundation.layout.width
import androidx.compose.foundation.interaction.MutableInteractionSource
import androidx.compose.foundation.interaction.collectIsHoveredAsState
import androidx.compose.foundation.shape.CircleShape
import androidx.compose.material.MaterialTheme
import androidx.compose.material.Surface
import androidx.compose.material.Text
import androidx.compose.runtime.Composable
import androidx.compose.runtime.getValue
import androidx.compose.runtime.remember
import androidx.compose.ui.Alignment
import androidx.compose.ui.Modifier
import androidx.compose.ui.draw.clip
import androidx.compose.ui.ExperimentalComposeUiApi
import androidx.compose.ui.graphics.Color
import androidx.compose.ui.graphics.painter.Painter
import androidx.compose.ui.input.key.Key
import androidx.compose.ui.input.key.key
import androidx.compose.ui.input.key.type
import androidx.compose.ui.input.key.KeyEventType
import androidx.compose.ui.input.key.onPreviewKeyEvent
import androidx.compose.ui.layout.ContentScale
import androidx.compose.ui.unit.dp
import example.imageviewer.core.FilterType
import example.imageviewer.model.AppState
import example.imageviewer.model.ContentState
import example.imageviewer.model.ScreenType
import example.imageviewer.ResString
import example.imageviewer.style.DarkGray
import example.imageviewer.style.Foreground
import example.imageviewer.style.MiniatureColor
import example.imageviewer.style.TranslucentBlack
import example.imageviewer.style.Transparent
import example.imageviewer.style.icBack
import example.imageviewer.style.icFilterBlurOff
import example.imageviewer.style.icFilterBlurOn
import example.imageviewer.style.icFilterGrayscaleOff
import example.imageviewer.style.icFilterGrayscaleOn
import example.imageviewer.style.icFilterPixelOff
import example.imageviewer.style.icFilterPixelOn
@Composable
fun FullscreenImage(
content: ContentState
) {
Column {
ToolBar(content.getSelectedImageName(), content)
Image(content)
}
if (!content.isContentReady()) {
LoadingScreen()
}
}
@Composable
fun ToolBar(
text: String,
content: ContentState
) {
val backButtonInteractionSource = remember { MutableInteractionSource() }
val backButtonHover by backButtonInteractionSource.collectIsHoveredAsState()
Surface(
color = MiniatureColor,
modifier = Modifier.height(44.dp)
) {
Row(modifier = Modifier.padding(end = 30.dp)) {
Surface(
color = Transparent,
modifier = Modifier.padding(start = 20.dp).align(Alignment.CenterVertically),
shape = CircleShape
) {
Tooltip(ResString.back) {
Clickable(
modifier = Modifier
.hoverable(backButtonInteractionSource)
.background(color = if (backButtonHover) TranslucentBlack else Transparent),
onClick = {
if (content.isContentReady()) {
content.restoreMainImage()
AppState.screenState(ScreenType.MainScreen)
}
}) {
Image(
icBack(),
contentDescription = null,
modifier = Modifier.size(38.dp)
)
}
}
}
Text(
text,
color = Foreground,
maxLines = 1,
modifier = Modifier.padding(start = 30.dp).weight(1f)
.align(Alignment.CenterVertically),
style = MaterialTheme.typography.body1
)
Surface(
color = Color(255, 255, 255, 40),
modifier = Modifier.size(154.dp, 38.dp)
.align(Alignment.CenterVertically),
shape = CircleShape
) {
val state = rememberScrollState(0)
Row(modifier = Modifier.horizontalScroll(state)) {
Row {
for (type in FilterType.values()) {
FilterButton(content, type)
}
}
}
}
}
}
}
@Composable
fun FilterButton(
content: ContentState,
type: FilterType,
modifier: Modifier = Modifier.size(38.dp)
) {
val interactionSource = remember { MutableInteractionSource() }
val filterButtonHover by interactionSource.collectIsHoveredAsState()
Box(
modifier = Modifier.background(color = Transparent).clip(CircleShape)
) {
Tooltip("$type") {
Clickable(
modifier = Modifier
.hoverable(interactionSource)
.background(color = if (filterButtonHover) TranslucentBlack else Transparent),
onClick = { content.toggleFilter(type)}
) {
Image(
getFilterImage(type = type, content = content),
contentDescription = null,
modifier
)
}
}
}
Spacer(Modifier.width(20.dp))
}
@Composable
fun getFilterImage(type: FilterType, content: ContentState): Painter {
return when (type) {
FilterType.GrayScale -> if (content.isFilterEnabled(type)) icFilterGrayscaleOn() else icFilterGrayscaleOff()
FilterType.Pixel -> if (content.isFilterEnabled(type)) icFilterPixelOn() else icFilterPixelOff()
FilterType.Blur -> if (content.isFilterEnabled(type)) icFilterBlurOn() else icFilterBlurOff()
}
}
@OptIn(ExperimentalComposeUiApi::class)
@Composable
fun Image(content: ContentState) {
val onUpdate = remember { { content.updateMainImage() } }
Surface(
color = DarkGray,
modifier = Modifier.fillMaxSize()
) {
Draggable(
onUpdate = onUpdate,
dragHandler = content.drag,
modifier = Modifier.fillMaxSize()
) {
Zoomable(
onUpdate = onUpdate,
scaleHandler = content.scale,
modifier = Modifier.fillMaxSize()
.onPreviewKeyEvent {
if (it.type == KeyEventType.KeyUp) {
when (it.key) {
Key.DirectionLeft -> {
content.swipePrevious()
}
Key.DirectionRight -> {
content.swipeNext()
}
}
}
false
}
) {
Image(
bitmap = content.getSelectedImage(),
contentDescription = null,
contentScale = ContentScale.Fit
)
}
}
}
}

250
experimental/examples/imageviewer/common/src/desktopMain/kotlin/example/imageviewer/view/MainScreen.kt

@ -1,250 +0,0 @@
package example.imageviewer.view
import androidx.compose.foundation.*
import androidx.compose.foundation.layout.Box
import androidx.compose.foundation.layout.Column
import androidx.compose.foundation.layout.Row
import androidx.compose.foundation.layout.Spacer
import androidx.compose.foundation.layout.fillMaxHeight
import androidx.compose.foundation.layout.fillMaxSize
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.interaction.MutableInteractionSource
import androidx.compose.foundation.interaction.collectIsHoveredAsState
import androidx.compose.foundation.shape.CircleShape
import androidx.compose.material.Card
import androidx.compose.material.Divider
import androidx.compose.material.MaterialTheme
import androidx.compose.material.Surface
import androidx.compose.material.Text
import androidx.compose.material.TopAppBar
import androidx.compose.runtime.Composable
import androidx.compose.runtime.getValue
import androidx.compose.runtime.remember
import androidx.compose.ui.Alignment
import androidx.compose.ui.Modifier
import androidx.compose.ui.graphics.painter.BitmapPainter
import androidx.compose.ui.graphics.RectangleShape
import androidx.compose.ui.graphics.toComposeImageBitmap
import androidx.compose.ui.layout.ContentScale
import androidx.compose.ui.unit.dp
import example.imageviewer.ResString
import example.imageviewer.model.AppState
import example.imageviewer.model.ContentState
import example.imageviewer.model.Picture
import example.imageviewer.model.ScreenType
import example.imageviewer.style.DarkGray
import example.imageviewer.style.DarkGreen
import example.imageviewer.style.Foreground
import example.imageviewer.style.LightGray
import example.imageviewer.style.MiniatureColor
import example.imageviewer.style.MiniatureHoverColor
import example.imageviewer.style.TranslucentBlack
import example.imageviewer.style.TranslucentWhite
import example.imageviewer.style.Transparent
import example.imageviewer.style.icDots
import example.imageviewer.style.icEmpty
import example.imageviewer.style.icRefresh
import example.imageviewer.utils.toByteArray
@Composable
fun MainScreen(content: ContentState) {
Column {
TopContent(content)
ScrollableArea(content)
}
if (!content.isContentReady()) {
LoadingScreen(ResString.loading)
}
}
@Composable
fun TopContent(content: ContentState) {
TitleBar(text = ResString.appName, content = content)
PreviewImage(content)
Spacer(modifier = Modifier.height(10.dp))
Divider()
Spacer(modifier = Modifier.height(5.dp))
}
@Composable
fun TitleBar(text: String, content: ContentState) {
val interactionSource = remember { MutableInteractionSource() }
val refreshButtonHover by interactionSource.collectIsHoveredAsState()
TopAppBar(
backgroundColor = DarkGreen,
title = {
Row(Modifier.height(50.dp)) {
Text(
text,
color = Foreground,
modifier = Modifier.weight(1f).align(Alignment.CenterVertically)
)
Surface(
color = Transparent,
modifier = Modifier.padding(end = 20.dp).align(Alignment.CenterVertically),
shape = CircleShape
) {
Tooltip(ResString.refresh) {
Clickable(
modifier = Modifier
.hoverable(interactionSource)
.background(color = if (refreshButtonHover) TranslucentBlack else Transparent),
onClick = {
if (content.isContentReady()) {
content.refresh()
}
}
) {
Image(
icRefresh(),
contentDescription = null,
modifier = Modifier.size(35.dp)
)
}
}
}
}
})
}
@Composable
fun PreviewImage(content: ContentState) {
Clickable(
modifier = Modifier.background(color = DarkGray),
onClick = {
AppState.screenState(ScreenType.FullscreenImage)
}
) {
Card(
backgroundColor = Transparent,
modifier = Modifier.height(250.dp),
shape = RectangleShape,
elevation = 1.dp
) {
Image(
if (content.isMainImageEmpty())
icEmpty()
else
BitmapPainter(content.getSelectedImage()),
contentDescription = null,
modifier = Modifier
.fillMaxWidth().padding(start = 1.dp, top = 1.dp, end = 1.dp, bottom = 5.dp),
contentScale = ContentScale.Fit
)
}
}
}
@Composable
fun Miniature(
picture: Picture,
content: ContentState
) {
val cardHoverInteractionSource = remember { MutableInteractionSource() }
val cardHover by cardHoverInteractionSource.collectIsHoveredAsState()
val infoButtonInteractionSource = remember { MutableInteractionSource() }
val infoButtonHover by infoButtonInteractionSource.collectIsHoveredAsState()
Card(
backgroundColor = if (cardHover) MiniatureHoverColor else MiniatureColor,
modifier = Modifier.padding(start = 10.dp, end = 18.dp).height(70.dp)
.fillMaxWidth()
.hoverable(cardHoverInteractionSource)
.clickable {
content.setMainImage(picture)
},
shape = RectangleShape
) {
Row(modifier = Modifier.padding(end = 30.dp)) {
Clickable(
onClick = {
content.fullscreen(picture)
}
) {
Image(
org.jetbrains.skia.Image.makeFromEncoded(
toByteArray(picture.image)
).toComposeImageBitmap(),
contentDescription = null,
modifier = Modifier.height(70.dp)
.width(90.dp)
.padding(start = 1.dp, top = 1.dp, end = 1.dp, bottom = 1.dp),
contentScale = ContentScale.Crop
)
}
Text(
text = picture.name,
color = Foreground,
modifier = Modifier
.weight(1f)
.align(Alignment.CenterVertically)
.padding(start = 16.dp),
style = MaterialTheme.typography.body1
)
Clickable(
modifier = Modifier.height(70.dp)
.width(30.dp)
.hoverable(infoButtonInteractionSource)
.background(color = if (infoButtonHover) TranslucentWhite else Transparent),
onClick = {
showPopUpMessage(
"${ResString.picture} " +
"${picture.name} \n" +
"${ResString.size} " +
"${picture.width}x${picture.height} " +
"${ResString.pixels}"
)
}
) {
Image(
icDots(),
contentDescription = null,
modifier = Modifier.height(70.dp)
.width(30.dp)
.padding(start = 1.dp, top = 25.dp, end = 1.dp, bottom = 25.dp),
contentScale = ContentScale.FillHeight
)
}
}
}
}
@Composable
fun ScrollableArea(content: ContentState) {
Box(
modifier = Modifier.fillMaxSize()
.padding(end = 8.dp)
) {
val stateVertical = rememberScrollState(0)
Column(modifier = Modifier.verticalScroll(stateVertical)) {
var index = 1
Column {
for (picture in content.getMiniatures()) {
Miniature(
picture = picture,
content = content
)
Spacer(modifier = Modifier.height(5.dp))
index++
}
}
}
VerticalScrollbar(
adapter = rememberScrollbarAdapter(stateVertical),
modifier = Modifier.align(Alignment.CenterEnd)
.fillMaxHeight()
)
}
}
@Composable
fun Divider() {
Divider(
color = LightGray,
modifier = Modifier.padding(start = 10.dp, end = 10.dp)
)
}

67
experimental/examples/imageviewer/common/src/desktopMain/kotlin/example/imageviewer/view/Zoomable.kt

@ -1,67 +0,0 @@
package example.imageviewer.view
import androidx.compose.foundation.focusable
import androidx.compose.foundation.gestures.detectTapGestures
import androidx.compose.material.Surface
import androidx.compose.runtime.Composable
import androidx.compose.runtime.DisposableEffect
import androidx.compose.runtime.remember
import androidx.compose.ui.Modifier
import androidx.compose.ui.focus.FocusRequester
import androidx.compose.ui.focus.focusRequester
import androidx.compose.ui.input.key.Key
import androidx.compose.ui.input.key.key
import androidx.compose.ui.input.key.type
import androidx.compose.ui.input.key.KeyEventType
import androidx.compose.ui.input.key.onPreviewKeyEvent
import androidx.compose.ui.input.pointer.pointerInput
import androidx.compose.ui.ExperimentalComposeUiApi
import example.imageviewer.style.Transparent
@OptIn(ExperimentalComposeUiApi::class)
@Composable
fun Zoomable(
scaleHandler: ScaleHandler,
modifier: Modifier = Modifier,
onUpdate: (() -> Unit)? = null,
children: @Composable() () -> Unit
) {
val focusRequester = FocusRequester()
Surface(
color = Transparent,
modifier = modifier.onPreviewKeyEvent {
if (it.type == KeyEventType.KeyUp) {
when (it.key) {
Key.I -> {
scaleHandler.onScale(1.2f)
onUpdate?.invoke()
}
Key.O -> {
scaleHandler.onScale(0.8f)
onUpdate?.invoke()
}
Key.R -> {
scaleHandler.reset()
onUpdate?.invoke()
}
}
}
false
}
.focusRequester(focusRequester)
.focusable()
.pointerInput(Unit) {
detectTapGestures(onDoubleTap = { scaleHandler.reset() }) {
focusRequester.requestFocus()
}
}
) {
children()
}
DisposableEffect(Unit) {
focusRequester.requestFocus()
onDispose { }
}
}

BIN
experimental/examples/imageviewer/common/src/desktopMain/resources/images/back.png

Binary file not shown.

Before

Width:  |  Height:  |  Size: 5.9 KiB

BIN
experimental/examples/imageviewer/common/src/desktopMain/resources/images/blur_off.png

Binary file not shown.

Before

Width:  |  Height:  |  Size: 35 KiB

BIN
experimental/examples/imageviewer/common/src/desktopMain/resources/images/blur_on.png

Binary file not shown.

Before

Width:  |  Height:  |  Size: 38 KiB

BIN
experimental/examples/imageviewer/common/src/desktopMain/resources/images/dots.png

Binary file not shown.

Before

Width:  |  Height:  |  Size: 2.2 KiB

BIN
experimental/examples/imageviewer/common/src/desktopMain/resources/images/empty.png

Binary file not shown.

Before

Width:  |  Height:  |  Size: 49 KiB

BIN
experimental/examples/imageviewer/common/src/desktopMain/resources/images/filter_unknown.png

Binary file not shown.

Before

Width:  |  Height:  |  Size: 11 KiB

BIN
experimental/examples/imageviewer/common/src/desktopMain/resources/images/grayscale_off.png

Binary file not shown.

Before

Width:  |  Height:  |  Size: 9.1 KiB

BIN
experimental/examples/imageviewer/common/src/desktopMain/resources/images/grayscale_on.png

Binary file not shown.

Before

Width:  |  Height:  |  Size: 10 KiB

BIN
experimental/examples/imageviewer/common/src/desktopMain/resources/images/pixel_off.png

Binary file not shown.

Before

Width:  |  Height:  |  Size: 6.8 KiB

BIN
experimental/examples/imageviewer/common/src/desktopMain/resources/images/pixel_on.png

Binary file not shown.

Before

Width:  |  Height:  |  Size: 8.0 KiB

BIN
experimental/examples/imageviewer/common/src/desktopMain/resources/images/refresh.png

Binary file not shown.

Before

Width:  |  Height:  |  Size: 8.2 KiB

59
experimental/examples/imageviewer/desktop/src/jvmMain/kotlin/example/imageviewer/Main.kt

@ -1,59 +0,0 @@
package example.imageviewer
import androidx.compose.material.MaterialTheme
import androidx.compose.runtime.remember
import androidx.compose.ui.Alignment
import androidx.compose.ui.Modifier
import androidx.compose.ui.window.Window
import androidx.compose.ui.window.WindowState
import androidx.compose.ui.window.WindowPosition
import androidx.compose.ui.window.application
import androidx.compose.ui.window.rememberWindowState
import example.imageviewer.model.ContentState
import example.imageviewer.style.icAppRounded
import example.imageviewer.utils.getPreferredWindowSize
import example.imageviewer.view.AppUI
import example.imageviewer.view.SplashUI
fun main() = application {
val state = rememberWindowState()
val content = remember {
ContentState.applyContent(
state,
"https://raw.githubusercontent.com/JetBrains/compose-jb/master/artwork/imageviewerrepo/fetching.list"
)
}
val icon = icAppRounded()
if (content.isAppReady()) {
Window(
onCloseRequest = ::exitApplication,
title = "Image Viewer",
state = WindowState(
position = WindowPosition.Aligned(Alignment.Center),
size = getPreferredWindowSize(800, 1000)
),
icon = icon
) {
MaterialTheme {
AppUI(content)
}
}
} else {
Window(
onCloseRequest = ::exitApplication,
title = "Image Viewer",
state = WindowState(
position = WindowPosition.Aligned(Alignment.Center),
size = getPreferredWindowSize(800, 300)
),
undecorated = true,
icon = icon,
) {
MaterialTheme {
SplashUI()
}
}
}
}

6
experimental/examples/imageviewer/desktop/build.gradle.kts → experimental/examples/imageviewer/desktopApp/build.gradle.kts

@ -1,7 +1,7 @@
import org.jetbrains.compose.desktop.application.dsl.TargetFormat
plugins {
kotlin("multiplatform") // kotlin("jvm") doesn't work well in IDEA/AndroidStudio (https://github.com/JetBrains/compose-jb/issues/22)
kotlin("multiplatform")
id("org.jetbrains.compose")
}
@ -10,10 +10,10 @@ kotlin {
withJava()
}
sourceSets {
named("jvmMain") {
val jvmMain by getting {
dependencies {
implementation(compose.desktop.currentOs)
implementation(project(":common"))
implementation(project(":shared"))
}
}
}

0
experimental/examples/imageviewer/desktop/rules.pro → experimental/examples/imageviewer/desktopApp/rules.pro

11
experimental/examples/imageviewer/desktopApp/src/jvmMain/kotlin/example/imageviewer/Main.kt

@ -0,0 +1,11 @@
package example.imageviewer
import androidx.compose.material.MaterialTheme
import androidx.compose.ui.window.application
import example.imageviewer.view.ImageViewerDesktop
fun main() = application {
MaterialTheme {
ImageViewerDesktop()
}
}

36
experimental/examples/imageviewer/gradle.properties

@ -1,24 +1,18 @@
# Project-wide Gradle settings.
# IDE (e.g. Android Studio) users:
# Gradle settings configured through the IDE *will override*
# any settings specified in this file.
# For more details on how to configure your build environment visit
# http://www.gradle.org/docs/current/userguide/build_environment.html
# Specifies the JVM arguments used for the daemon process.
# The setting is particularly useful for tweaking memory settings.
org.gradle.jvmargs=-Xmx2048m
# When configured, Gradle will run in incubating parallel mode.
# This option should only be used with decoupled projects. More details, visit
# http://www.gradle.org/docs/current/userguide/multi_project_builds.html#sec:decoupled_projects
# org.gradle.parallel=true
# AndroidX package structure to make it clearer which packages are bundled with the
# Android operating system, and which are packaged with your app"s APK
# https://developer.android.com/topic/libraries/support-library/androidx-rn
android.useAndroidX=true
# Automatically convert third-party libraries to use AndroidX
android.enableJetifier=true
# Kotlin code style for this project: "official" or "obsolete":
kotlin.code.style=official
xcodeproj=iosApp
kotlin.native.cocoapods.generate.wrapper=true
android.useAndroidX=true
org.gradle.jvmargs=-Xmx3g
org.jetbrains.compose.experimental.jscanvas.enabled=true
org.jetbrains.compose.experimental.macos.enabled=true
org.jetbrains.compose.experimental.uikit.enabled=true
kotlin.native.cacheKind=none
kotlin.native.useEmbeddableCompilerJar=true
kotlin.native.enableDependencyPropagation=false
kotlin.mpp.enableGranularSourceSetsMetadata=true
# Enable kotlin/native experimental memory model
kotlin.native.binary.memoryModel=experimental
kotlin.version=1.7.20
agp.version=7.1.3
compose.version=1.2.1
compose.version=1.2.2
ktor.version=2.2.1

BIN
experimental/examples/imageviewer/gradle/wrapper/gradle-wrapper.jar vendored

Binary file not shown.

263
experimental/examples/imageviewer/gradlew vendored

@ -1,7 +1,7 @@
#!/usr/bin/env sh
#!/bin/sh
#
# Copyright 2015 the original author or authors.
# Copyright © 2015-2021 the original authors.
#
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
@ -17,67 +17,101 @@
#
##############################################################################
##
## Gradle start up script for UN*X
##
#
# Gradle start up script for POSIX generated by Gradle.
#
# Important for running:
#
# (1) You need a POSIX-compliant shell to run this script. If your /bin/sh is
# noncompliant, but you have some other compliant shell such as ksh or
# bash, then to run this script, type that shell name before the whole
# command line, like:
#
# ksh Gradle
#
# Busybox and similar reduced shells will NOT work, because this script
# requires all of these POSIX shell features:
# * functions;
# * expansions «$var», «${var}», «${var:-default}», «${var+SET}»,
# «${var#prefix}», «${var%suffix}», and «$( cmd )»;
# * compound commands having a testable exit status, especially «case»;
# * various built-in commands including «command», «set», and «ulimit».
#
# Important for patching:
#
# (2) This script targets any POSIX shell, so it avoids extensions provided
# by Bash, Ksh, etc; in particular arrays are avoided.
#
# The "traditional" practice of packing multiple parameters into a
# space-separated string is a well documented source of bugs and security
# problems, so this is (mostly) avoided, by progressively accumulating
# options in "$@", and eventually passing that to Java.
#
# Where the inherited environment variables (DEFAULT_JVM_OPTS, JAVA_OPTS,
# and GRADLE_OPTS) rely on word-splitting, this is performed explicitly;
# see the in-line comments for details.
#
# There are tweaks for specific operating systems such as AIX, CygWin,
# Darwin, MinGW, and NonStop.
#
# (3) This script is generated from the Groovy template
# https://github.com/gradle/gradle/blob/master/subprojects/plugins/src/main/resources/org/gradle/api/internal/plugins/unixStartScript.txt
# within the Gradle project.
#
# You can find Gradle at https://github.com/gradle/gradle/.
#
##############################################################################
# Attempt to set APP_HOME
# Resolve links: $0 may be a link
PRG="$0"
# Need this for relative symlinks.
while [ -h "$PRG" ] ; do
ls=`ls -ld "$PRG"`
link=`expr "$ls" : '.*-> \(.*\)$'`
if expr "$link" : '/.*' > /dev/null; then
PRG="$link"
else
PRG=`dirname "$PRG"`"/$link"
fi
app_path=$0
# Need this for daisy-chained symlinks.
while
APP_HOME=${app_path%"${app_path##*/}"} # leaves a trailing /; empty if no leading path
[ -h "$app_path" ]
do
ls=$( ls -ld "$app_path" )
link=${ls#*' -> '}
case $link in #(
/*) app_path=$link ;; #(
*) app_path=$APP_HOME$link ;;
esac
done
SAVED="`pwd`"
cd "`dirname \"$PRG\"`/" >/dev/null
APP_HOME="`pwd -P`"
cd "$SAVED" >/dev/null
APP_HOME=$( cd "${APP_HOME:-./}" && pwd -P ) || exit
APP_NAME="Gradle"
APP_BASE_NAME=`basename "$0"`
APP_BASE_NAME=${0##*/}
# Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script.
DEFAULT_JVM_OPTS='"-Xmx64m" "-Xms64m"'
# Use the maximum available, or set MAX_FD != -1 to use that value.
MAX_FD="maximum"
MAX_FD=maximum
warn () {
echo "$*"
}
} >&2
die () {
echo
echo "$*"
echo
exit 1
}
} >&2
# OS specific support (must be 'true' or 'false').
cygwin=false
msys=false
darwin=false
nonstop=false
case "`uname`" in
CYGWIN* )
cygwin=true
;;
Darwin* )
darwin=true
;;
MINGW* )
msys=true
;;
NONSTOP* )
nonstop=true
;;
case "$( uname )" in #(
CYGWIN* ) cygwin=true ;; #(
Darwin* ) darwin=true ;; #(
MSYS* | MINGW* ) msys=true ;; #(
NONSTOP* ) nonstop=true ;;
esac
CLASSPATH=$APP_HOME/gradle/wrapper/gradle-wrapper.jar
@ -87,9 +121,9 @@ CLASSPATH=$APP_HOME/gradle/wrapper/gradle-wrapper.jar
if [ -n "$JAVA_HOME" ] ; then
if [ -x "$JAVA_HOME/jre/sh/java" ] ; then
# IBM's JDK on AIX uses strange locations for the executables
JAVACMD="$JAVA_HOME/jre/sh/java"
JAVACMD=$JAVA_HOME/jre/sh/java
else
JAVACMD="$JAVA_HOME/bin/java"
JAVACMD=$JAVA_HOME/bin/java
fi
if [ ! -x "$JAVACMD" ] ; then
die "ERROR: JAVA_HOME is set to an invalid directory: $JAVA_HOME
@ -98,7 +132,7 @@ Please set the JAVA_HOME variable in your environment to match the
location of your Java installation."
fi
else
JAVACMD="java"
JAVACMD=java
which java >/dev/null 2>&1 || die "ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH.
Please set the JAVA_HOME variable in your environment to match the
@ -106,80 +140,101 @@ location of your Java installation."
fi
# Increase the maximum file descriptors if we can.
if [ "$cygwin" = "false" -a "$darwin" = "false" -a "$nonstop" = "false" ] ; then
MAX_FD_LIMIT=`ulimit -H -n`
if [ $? -eq 0 ] ; then
if [ "$MAX_FD" = "maximum" -o "$MAX_FD" = "max" ] ; then
MAX_FD="$MAX_FD_LIMIT"
fi
ulimit -n $MAX_FD
if [ $? -ne 0 ] ; then
warn "Could not set maximum file descriptor limit: $MAX_FD"
fi
else
warn "Could not query maximum file descriptor limit: $MAX_FD_LIMIT"
fi
if ! "$cygwin" && ! "$darwin" && ! "$nonstop" ; then
case $MAX_FD in #(
max*)
MAX_FD=$( ulimit -H -n ) ||
warn "Could not query maximum file descriptor limit"
esac
case $MAX_FD in #(
'' | soft) :;; #(
*)
ulimit -n "$MAX_FD" ||
warn "Could not set maximum file descriptor limit to $MAX_FD"
esac
fi
# For Darwin, add options to specify how the application appears in the dock
if $darwin; then
GRADLE_OPTS="$GRADLE_OPTS \"-Xdock:name=$APP_NAME\" \"-Xdock:icon=$APP_HOME/media/gradle.icns\""
fi
# Collect all arguments for the java command, stacking in reverse order:
# * args from the command line
# * the main class name
# * -classpath
# * -D...appname settings
# * --module-path (only if needed)
# * DEFAULT_JVM_OPTS, JAVA_OPTS, and GRADLE_OPTS environment variables.
# For Cygwin or MSYS, switch paths to Windows format before running java
if [ "$cygwin" = "true" -o "$msys" = "true" ] ; then
APP_HOME=`cygpath --path --mixed "$APP_HOME"`
CLASSPATH=`cygpath --path --mixed "$CLASSPATH"`
JAVACMD=`cygpath --unix "$JAVACMD"`
# We build the pattern for arguments to be converted via cygpath
ROOTDIRSRAW=`find -L / -maxdepth 1 -mindepth 1 -type d 2>/dev/null`
SEP=""
for dir in $ROOTDIRSRAW ; do
ROOTDIRS="$ROOTDIRS$SEP$dir"
SEP="|"
done
OURCYGPATTERN="(^($ROOTDIRS))"
# Add a user-defined pattern to the cygpath arguments
if [ "$GRADLE_CYGPATTERN" != "" ] ; then
OURCYGPATTERN="$OURCYGPATTERN|($GRADLE_CYGPATTERN)"
fi
# Now convert the arguments - kludge to limit ourselves to /bin/sh
i=0
for arg in "$@" ; do
CHECK=`echo "$arg"|egrep -c "$OURCYGPATTERN" -`
CHECK2=`echo "$arg"|egrep -c "^-"` ### Determine if an option
if "$cygwin" || "$msys" ; then
APP_HOME=$( cygpath --path --mixed "$APP_HOME" )
CLASSPATH=$( cygpath --path --mixed "$CLASSPATH" )
if [ $CHECK -ne 0 ] && [ $CHECK2 -eq 0 ] ; then ### Added a condition
eval `echo args$i`=`cygpath --path --ignore --mixed "$arg"`
else
eval `echo args$i`="\"$arg\""
JAVACMD=$( cygpath --unix "$JAVACMD" )
# Now convert the arguments - kludge to limit ourselves to /bin/sh
for arg do
if
case $arg in #(
-*) false ;; # don't mess with options #(
/?*) t=${arg#/} t=/${t%%/*} # looks like a POSIX filepath
[ -e "$t" ] ;; #(
*) false ;;
esac
then
arg=$( cygpath --path --ignore --mixed "$arg" )
fi
i=`expr $i + 1`
# Roll the args list around exactly as many times as the number of
# args, so each arg winds up back in the position where it started, but
# possibly modified.
#
# NB: a `for` loop captures its iteration list before it begins, so
# changing the positional parameters here affects neither the number of
# iterations, nor the values presented in `arg`.
shift # remove old arg
set -- "$@" "$arg" # push replacement arg
done
case $i in
0) set -- ;;
1) set -- "$args0" ;;
2) set -- "$args0" "$args1" ;;
3) set -- "$args0" "$args1" "$args2" ;;
4) set -- "$args0" "$args1" "$args2" "$args3" ;;
5) set -- "$args0" "$args1" "$args2" "$args3" "$args4" ;;
6) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" ;;
7) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" ;;
8) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" "$args7" ;;
9) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" "$args7" "$args8" ;;
esac
fi
# Escape application args
save () {
for i do printf %s\\n "$i" | sed "s/'/'\\\\''/g;1s/^/'/;\$s/\$/' \\\\/" ; done
echo " "
}
APP_ARGS=`save "$@"`
# Collect all arguments for the java command;
# * $DEFAULT_JVM_OPTS, $JAVA_OPTS, and $GRADLE_OPTS can contain fragments of
# shell script including quotes and variable substitutions, so put them in
# double quotes to make sure that they get re-expanded; and
# * put everything else in single quotes, so that it's not re-expanded.
set -- \
"-Dorg.gradle.appname=$APP_BASE_NAME" \
-classpath "$CLASSPATH" \
org.gradle.wrapper.GradleWrapperMain \
"$@"
# Stop when "xargs" is not available.
if ! command -v xargs >/dev/null 2>&1
then
die "xargs is not available"
fi
# Use "xargs" to parse quoted args.
#
# With -n1 it outputs one arg per line, with the quotes and backslashes removed.
#
# In Bash we could simply go:
#
# readarray ARGS < <( xargs -n1 <<<"$var" ) &&
# set -- "${ARGS[@]}" "$@"
#
# but POSIX shell has neither arrays nor command substitution, so instead we
# post-process each arg (as a line of input to sed) to backslash-escape any
# character that might be a shell metacharacter, then use eval to reverse
# that process (while maintaining the separation between arguments), and wrap
# the whole thing up as a single "set" statement.
#
# This will of course break if any of these variables contains a newline or
# an unmatched quote.
#
# Collect all arguments for the java command, following the shell quoting and substitution rules
eval set -- $DEFAULT_JVM_OPTS $JAVA_OPTS $GRADLE_OPTS "\"-Dorg.gradle.appname=$APP_BASE_NAME\"" -classpath "\"$CLASSPATH\"" org.gradle.wrapper.GradleWrapperMain "$APP_ARGS"
eval "set -- $(
printf '%s\n' "$DEFAULT_JVM_OPTS $JAVA_OPTS $GRADLE_OPTS" |
xargs -n1 |
sed ' s~[^-[:alnum:]+,./:=@_]~\\&~g; ' |
tr '\n' ' '
)" '"$@"'
exec "$JAVACMD" "$@"

33
experimental/examples/imageviewer/gradlew.bat vendored

@ -14,7 +14,7 @@
@rem limitations under the License.
@rem
@if "%DEBUG%" == "" @echo off
@if "%DEBUG%"=="" @echo off
@rem ##########################################################################
@rem
@rem Gradle startup script for Windows
@ -25,7 +25,7 @@
if "%OS%"=="Windows_NT" setlocal
set DIRNAME=%~dp0
if "%DIRNAME%" == "" set DIRNAME=.
if "%DIRNAME%"=="" set DIRNAME=.
set APP_BASE_NAME=%~n0
set APP_HOME=%DIRNAME%
@ -40,7 +40,7 @@ if defined JAVA_HOME goto findJavaFromJavaHome
set JAVA_EXE=java.exe
%JAVA_EXE% -version >NUL 2>&1
if "%ERRORLEVEL%" == "0" goto init
if %ERRORLEVEL% equ 0 goto execute
echo.
echo ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH.
@ -54,7 +54,7 @@ goto fail
set JAVA_HOME=%JAVA_HOME:"=%
set JAVA_EXE=%JAVA_HOME%/bin/java.exe
if exist "%JAVA_EXE%" goto init
if exist "%JAVA_EXE%" goto execute
echo.
echo ERROR: JAVA_HOME is set to an invalid directory: %JAVA_HOME%
@ -64,21 +64,6 @@ echo location of your Java installation.
goto fail
:init
@rem Get command-line arguments, handling Windows variants
if not "%OS%" == "Windows_NT" goto win9xME_args
:win9xME_args
@rem Slurp the command line arguments.
set CMD_LINE_ARGS=
set _SKIP=2
:win9xME_args_slurp
if "x%~1" == "x" goto execute
set CMD_LINE_ARGS=%*
:execute
@rem Setup the command line
@ -86,17 +71,19 @@ set CLASSPATH=%APP_HOME%\gradle\wrapper\gradle-wrapper.jar
@rem Execute Gradle
"%JAVA_EXE%" %DEFAULT_JVM_OPTS% %JAVA_OPTS% %GRADLE_OPTS% "-Dorg.gradle.appname=%APP_BASE_NAME%" -classpath "%CLASSPATH%" org.gradle.wrapper.GradleWrapperMain %CMD_LINE_ARGS%
"%JAVA_EXE%" %DEFAULT_JVM_OPTS% %JAVA_OPTS% %GRADLE_OPTS% "-Dorg.gradle.appname=%APP_BASE_NAME%" -classpath "%CLASSPATH%" org.gradle.wrapper.GradleWrapperMain %*
:end
@rem End local scope for the variables with windows NT shell
if "%ERRORLEVEL%"=="0" goto mainEnd
if %ERRORLEVEL% equ 0 goto mainEnd
:fail
rem Set variable GRADLE_EXIT_CONSOLE if you need the _script_ return code instead of
rem the _cmd.exe /c_ return code!
if not "" == "%GRADLE_EXIT_CONSOLE%" exit 1
exit /b 1
set EXIT_CODE=%ERRORLEVEL%
if %EXIT_CODE% equ 0 set EXIT_CODE=1
if not ""=="%GRADLE_EXIT_CONSOLE%" exit %EXIT_CODE%
exit /b %EXIT_CODE%
:mainEnd
if "%OS%"=="Windows_NT" endlocal

1
experimental/examples/imageviewer/iosApp/Configuration/TeamId.xcconfig

@ -0,0 +1 @@
TEAM_ID=

398
experimental/examples/imageviewer/iosApp/Imageviewer.xcodeproj/project.pbxproj

@ -0,0 +1,398 @@
// !$*UTF8*$!
{
archiveVersion = 1;
classes = {
};
objectVersion = 50;
objects = {
/* Begin PBXBuildFile section */
2152FB042600AC8F00CF470E /* iosApp.swift in Sources */ = {isa = PBXBuildFile; fileRef = 2152FB032600AC8F00CF470E /* iosApp.swift */; };
C1FC908188C4E8695729CB06 /* Pods_Imageviewer.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 8DE96E47030356CE6AD9794A /* Pods_Imageviewer.framework */; };
/* End PBXBuildFile section */
/* Begin PBXFileReference section */
1EB65E27D2C0F884D0A1A133 /* Pods-Imageviewer.debug.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-Imageviewer.debug.xcconfig"; path = "Target Support Files/Pods-Imageviewer/Pods-Imageviewer.debug.xcconfig"; sourceTree = "<group>"; };
2152FB032600AC8F00CF470E /* iosApp.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = iosApp.swift; sourceTree = "<group>"; };
3D7A606AB0AD7636269BD9D0 /* Pods-Imageviewer.release.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-Imageviewer.release.xcconfig"; path = "Target Support Files/Pods-Imageviewer/Pods-Imageviewer.release.xcconfig"; sourceTree = "<group>"; };
7555FF7B242A565900829871 /* Imageviewer.app */ = {isa = PBXFileReference; explicitFileType = wrapper.application; includeInIndex = 0; path = Imageviewer.app; sourceTree = BUILT_PRODUCTS_DIR; };
7555FF8C242A565B00829871 /* Info.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist.xml; path = Info.plist; sourceTree = "<group>"; };
8DE96E47030356CE6AD9794A /* Pods_Imageviewer.framework */ = {isa = PBXFileReference; explicitFileType = wrapper.framework; includeInIndex = 0; path = Pods_Imageviewer.framework; sourceTree = BUILT_PRODUCTS_DIR; };
AB3632DC29227652001CCB65 /* TeamId.xcconfig */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.xcconfig; path = TeamId.xcconfig; sourceTree = "<group>"; };
/* End PBXFileReference section */
/* Begin PBXFrameworksBuildPhase section */
9964867F0862B4D9FB6ABFC7 /* Frameworks */ = {
isa = PBXFrameworksBuildPhase;
buildActionMask = 2147483647;
files = (
C1FC908188C4E8695729CB06 /* Pods_Imageviewer.framework in Frameworks */,
);
runOnlyForDeploymentPostprocessing = 0;
};
/* End PBXFrameworksBuildPhase section */
/* Begin PBXGroup section */
7555FF72242A565900829871 = {
isa = PBXGroup;
children = (
AB1DB47929225F7C00F7AF9C /* Configuration */,
7555FF7D242A565900829871 /* iosApp */,
7555FF7C242A565900829871 /* Products */,
E1DAFBE8E1CFC0878361EF0E /* Pods */,
B62309C7396AD7BF607A63B2 /* Frameworks */,
);
sourceTree = "<group>";
};
7555FF7C242A565900829871 /* Products */ = {
isa = PBXGroup;
children = (
7555FF7B242A565900829871 /* Imageviewer.app */,
);
name = Products;
sourceTree = "<group>";
};
7555FF7D242A565900829871 /* iosApp */ = {
isa = PBXGroup;
children = (
7555FF8C242A565B00829871 /* Info.plist */,
2152FB032600AC8F00CF470E /* iosApp.swift */,
);
path = iosApp;
sourceTree = "<group>";
};
AB1DB47929225F7C00F7AF9C /* Configuration */ = {
isa = PBXGroup;
children = (
AB3632DC29227652001CCB65 /* TeamId.xcconfig */,
);
path = Configuration;
sourceTree = "<group>";
};
B62309C7396AD7BF607A63B2 /* Frameworks */ = {
isa = PBXGroup;
children = (
8DE96E47030356CE6AD9794A /* Pods_Imageviewer.framework */,
);
name = Frameworks;
sourceTree = "<group>";
};
E1DAFBE8E1CFC0878361EF0E /* Pods */ = {
isa = PBXGroup;
children = (
1EB65E27D2C0F884D0A1A133 /* Pods-Imageviewer.debug.xcconfig */,
3D7A606AB0AD7636269BD9D0 /* Pods-Imageviewer.release.xcconfig */,
);
path = Pods;
sourceTree = "<group>";
};
/* End PBXGroup section */
/* Begin PBXNativeTarget section */
7555FF7A242A565900829871 /* Imageviewer */ = {
isa = PBXNativeTarget;
buildConfigurationList = 7555FFA5242A565B00829871 /* Build configuration list for PBXNativeTarget "Imageviewer" */;
buildPhases = (
E8D673591E7196AEA2EA10E2 /* [CP] Check Pods Manifest.lock */,
7555FF77242A565900829871 /* Sources */,
7555FF79242A565900829871 /* Resources */,
9964867F0862B4D9FB6ABFC7 /* Frameworks */,
F34398AEB6C0D136D245A061 /* [CP] Copy Pods Resources */,
);
buildRules = (
);
dependencies = (
);
name = Imageviewer;
productName = iosApp;
productReference = 7555FF7B242A565900829871 /* Imageviewer.app */;
productType = "com.apple.product-type.application";
};
/* End PBXNativeTarget section */
/* Begin PBXProject section */
7555FF73242A565900829871 /* Project object */ = {
isa = PBXProject;
attributes = {
LastSwiftUpdateCheck = 1130;
LastUpgradeCheck = 1130;
ORGANIZATIONNAME = org.jetbrains;
TargetAttributes = {
7555FF7A242A565900829871 = {
CreatedOnToolsVersion = 11.3.1;
};
};
};
buildConfigurationList = 7555FF76242A565900829871 /* Build configuration list for PBXProject "Imageviewer" */;
compatibilityVersion = "Xcode 9.3";
developmentRegion = en;
hasScannedForEncodings = 0;
knownRegions = (
en,
Base,
);
mainGroup = 7555FF72242A565900829871;
productRefGroup = 7555FF7C242A565900829871 /* Products */;
projectDirPath = "";
projectRoot = "";
targets = (
7555FF7A242A565900829871 /* Imageviewer */,
);
};
/* End PBXProject section */
/* Begin PBXResourcesBuildPhase section */
7555FF79242A565900829871 /* Resources */ = {
isa = PBXResourcesBuildPhase;
buildActionMask = 2147483647;
files = (
);
runOnlyForDeploymentPostprocessing = 0;
};
/* End PBXResourcesBuildPhase section */
/* Begin PBXShellScriptBuildPhase section */
E8D673591E7196AEA2EA10E2 /* [CP] Check Pods Manifest.lock */ = {
isa = PBXShellScriptBuildPhase;
buildActionMask = 2147483647;
files = (
);
inputFileListPaths = (
);
inputPaths = (
"${PODS_PODFILE_DIR_PATH}/Podfile.lock",
"${PODS_ROOT}/Manifest.lock",
);
name = "[CP] Check Pods Manifest.lock";
outputFileListPaths = (
);
outputPaths = (
"$(DERIVED_FILE_DIR)/Pods-Imageviewer-checkManifestLockResult.txt",
);
runOnlyForDeploymentPostprocessing = 0;
shellPath = /bin/sh;
shellScript = "diff \"${PODS_PODFILE_DIR_PATH}/Podfile.lock\" \"${PODS_ROOT}/Manifest.lock\" > /dev/null\nif [ $? != 0 ] ; then\n # print error to STDERR\n echo \"error: The sandbox is not in sync with the Podfile.lock. Run 'pod install' or update your CocoaPods installation.\" >&2\n exit 1\nfi\n# This output is used by Xcode 'outputs' to avoid re-running this script phase.\necho \"SUCCESS\" > \"${SCRIPT_OUTPUT_FILE_0}\"\n";
showEnvVarsInLog = 0;
};
F34398AEB6C0D136D245A061 /* [CP] Copy Pods Resources */ = {
isa = PBXShellScriptBuildPhase;
buildActionMask = 2147483647;
files = (
);
inputFileListPaths = (
"${PODS_ROOT}/Target Support Files/Pods-Imageviewer/Pods-Imageviewer-resources-${CONFIGURATION}-input-files.xcfilelist",
);
name = "[CP] Copy Pods Resources";
outputFileListPaths = (
"${PODS_ROOT}/Target Support Files/Pods-Imageviewer/Pods-Imageviewer-resources-${CONFIGURATION}-output-files.xcfilelist",
);
runOnlyForDeploymentPostprocessing = 0;
shellPath = /bin/sh;
shellScript = "\"${PODS_ROOT}/Target Support Files/Pods-Imageviewer/Pods-Imageviewer-resources.sh\"\n";
showEnvVarsInLog = 0;
};
/* End PBXShellScriptBuildPhase section */
/* Begin PBXSourcesBuildPhase section */
7555FF77242A565900829871 /* Sources */ = {
isa = PBXSourcesBuildPhase;
buildActionMask = 2147483647;
files = (
2152FB042600AC8F00CF470E /* iosApp.swift in Sources */,
);
runOnlyForDeploymentPostprocessing = 0;
};
/* End PBXSourcesBuildPhase section */
/* Begin XCBuildConfiguration section */
7555FFA3242A565B00829871 /* Debug */ = {
isa = XCBuildConfiguration;
baseConfigurationReference = AB3632DC29227652001CCB65 /* TeamId.xcconfig */;
buildSettings = {
ALWAYS_SEARCH_USER_PATHS = NO;
CLANG_ANALYZER_NONNULL = YES;
CLANG_ANALYZER_NUMBER_OBJECT_CONVERSION = YES_AGGRESSIVE;
CLANG_CXX_LANGUAGE_STANDARD = "gnu++14";
CLANG_CXX_LIBRARY = "libc++";
CLANG_ENABLE_MODULES = YES;
CLANG_ENABLE_OBJC_ARC = YES;
CLANG_ENABLE_OBJC_WEAK = YES;
CLANG_WARN_BLOCK_CAPTURE_AUTORELEASING = YES;
CLANG_WARN_BOOL_CONVERSION = YES;
CLANG_WARN_COMMA = YES;
CLANG_WARN_CONSTANT_CONVERSION = YES;
CLANG_WARN_DEPRECATED_OBJC_IMPLEMENTATIONS = YES;
CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR;
CLANG_WARN_DOCUMENTATION_COMMENTS = YES;
CLANG_WARN_EMPTY_BODY = YES;
CLANG_WARN_ENUM_CONVERSION = YES;
CLANG_WARN_INFINITE_RECURSION = YES;
CLANG_WARN_INT_CONVERSION = YES;
CLANG_WARN_NON_LITERAL_NULL_CONVERSION = YES;
CLANG_WARN_OBJC_IMPLICIT_RETAIN_SELF = YES;
CLANG_WARN_OBJC_LITERAL_CONVERSION = YES;
CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR;
CLANG_WARN_QUOTED_INCLUDE_IN_FRAMEWORK_HEADER = YES;
CLANG_WARN_RANGE_LOOP_ANALYSIS = YES;
CLANG_WARN_STRICT_PROTOTYPES = YES;
CLANG_WARN_SUSPICIOUS_MOVE = YES;
CLANG_WARN_UNGUARDED_AVAILABILITY = YES_AGGRESSIVE;
CLANG_WARN_UNREACHABLE_CODE = YES;
CLANG_WARN__DUPLICATE_METHOD_MATCH = YES;
COPY_PHASE_STRIP = NO;
DEBUG_INFORMATION_FORMAT = "dwarf-with-dsym";
ENABLE_STRICT_OBJC_MSGSEND = YES;
ENABLE_TESTABILITY = YES;
GCC_C_LANGUAGE_STANDARD = gnu11;
GCC_DYNAMIC_NO_PIC = NO;
GCC_NO_COMMON_BLOCKS = YES;
GCC_OPTIMIZATION_LEVEL = 0;
GCC_PREPROCESSOR_DEFINITIONS = (
"DEBUG=1",
"$(inherited)",
);
GCC_WARN_64_TO_32_BIT_CONVERSION = YES;
GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR;
GCC_WARN_UNDECLARED_SELECTOR = YES;
GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE;
GCC_WARN_UNUSED_FUNCTION = YES;
GCC_WARN_UNUSED_VARIABLE = YES;
IPHONEOS_DEPLOYMENT_TARGET = 14.1;
MTL_ENABLE_DEBUG_INFO = INCLUDE_SOURCE;
MTL_FAST_MATH = YES;
ONLY_ACTIVE_ARCH = YES;
SDKROOT = iphoneos;
SWIFT_ACTIVE_COMPILATION_CONDITIONS = DEBUG;
SWIFT_OPTIMIZATION_LEVEL = "-Onone";
};
name = Debug;
};
7555FFA4242A565B00829871 /* Release */ = {
isa = XCBuildConfiguration;
baseConfigurationReference = AB3632DC29227652001CCB65 /* TeamId.xcconfig */;
buildSettings = {
ALWAYS_SEARCH_USER_PATHS = NO;
CLANG_ANALYZER_NONNULL = YES;
CLANG_ANALYZER_NUMBER_OBJECT_CONVERSION = YES_AGGRESSIVE;
CLANG_CXX_LANGUAGE_STANDARD = "gnu++14";
CLANG_CXX_LIBRARY = "libc++";
CLANG_ENABLE_MODULES = YES;
CLANG_ENABLE_OBJC_ARC = YES;
CLANG_ENABLE_OBJC_WEAK = YES;
CLANG_WARN_BLOCK_CAPTURE_AUTORELEASING = YES;
CLANG_WARN_BOOL_CONVERSION = YES;
CLANG_WARN_COMMA = YES;
CLANG_WARN_CONSTANT_CONVERSION = YES;
CLANG_WARN_DEPRECATED_OBJC_IMPLEMENTATIONS = YES;
CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR;
CLANG_WARN_DOCUMENTATION_COMMENTS = YES;
CLANG_WARN_EMPTY_BODY = YES;
CLANG_WARN_ENUM_CONVERSION = YES;
CLANG_WARN_INFINITE_RECURSION = YES;
CLANG_WARN_INT_CONVERSION = YES;
CLANG_WARN_NON_LITERAL_NULL_CONVERSION = YES;
CLANG_WARN_OBJC_IMPLICIT_RETAIN_SELF = YES;
CLANG_WARN_OBJC_LITERAL_CONVERSION = YES;
CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR;
CLANG_WARN_QUOTED_INCLUDE_IN_FRAMEWORK_HEADER = YES;
CLANG_WARN_RANGE_LOOP_ANALYSIS = YES;
CLANG_WARN_STRICT_PROTOTYPES = YES;
CLANG_WARN_SUSPICIOUS_MOVE = YES;
CLANG_WARN_UNGUARDED_AVAILABILITY = YES_AGGRESSIVE;
CLANG_WARN_UNREACHABLE_CODE = YES;
CLANG_WARN__DUPLICATE_METHOD_MATCH = YES;
COPY_PHASE_STRIP = NO;
DEBUG_INFORMATION_FORMAT = "dwarf-with-dsym";
ENABLE_NS_ASSERTIONS = NO;
ENABLE_STRICT_OBJC_MSGSEND = YES;
GCC_C_LANGUAGE_STANDARD = gnu11;
GCC_NO_COMMON_BLOCKS = YES;
GCC_WARN_64_TO_32_BIT_CONVERSION = YES;
GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR;
GCC_WARN_UNDECLARED_SELECTOR = YES;
GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE;
GCC_WARN_UNUSED_FUNCTION = YES;
GCC_WARN_UNUSED_VARIABLE = YES;
IPHONEOS_DEPLOYMENT_TARGET = 14.1;
MTL_ENABLE_DEBUG_INFO = NO;
MTL_FAST_MATH = YES;
SDKROOT = iphoneos;
SWIFT_COMPILATION_MODE = wholemodule;
SWIFT_OPTIMIZATION_LEVEL = "-O";
VALIDATE_PRODUCT = YES;
};
name = Release;
};
7555FFA6242A565B00829871 /* Debug */ = {
isa = XCBuildConfiguration;
baseConfigurationReference = 1EB65E27D2C0F884D0A1A133 /* Pods-Imageviewer.debug.xcconfig */;
buildSettings = {
ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon;
CODE_SIGN_IDENTITY = "Apple Development";
CODE_SIGN_STYLE = Automatic;
DEVELOPMENT_ASSET_PATHS = "";
DEVELOPMENT_TEAM = "${TEAM_ID}";
ENABLE_PREVIEWS = YES;
INFOPLIST_FILE = iosApp/Info.plist;
IPHONEOS_DEPLOYMENT_TARGET = 14.1;
LD_RUNPATH_SEARCH_PATHS = (
"$(inherited)",
"@executable_path/Frameworks",
);
PRODUCT_BUNDLE_IDENTIFIER = "org.jetbrains.Imageviewer${TEAM_ID}";
PRODUCT_NAME = "$(TARGET_NAME)";
PROVISIONING_PROFILE_SPECIFIER = "";
SWIFT_VERSION = 5.0;
TARGETED_DEVICE_FAMILY = "1,2";
};
name = Debug;
};
7555FFA7242A565B00829871 /* Release */ = {
isa = XCBuildConfiguration;
baseConfigurationReference = 3D7A606AB0AD7636269BD9D0 /* Pods-Imageviewer.release.xcconfig */;
buildSettings = {
ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon;
CODE_SIGN_IDENTITY = "Apple Development";
CODE_SIGN_STYLE = Automatic;
DEVELOPMENT_ASSET_PATHS = "";
DEVELOPMENT_TEAM = "${TEAM_ID}";
ENABLE_PREVIEWS = YES;
INFOPLIST_FILE = iosApp/Info.plist;
IPHONEOS_DEPLOYMENT_TARGET = 14.1;
LD_RUNPATH_SEARCH_PATHS = (
"$(inherited)",
"@executable_path/Frameworks",
);
PRODUCT_BUNDLE_IDENTIFIER = "org.jetbrains.Imageviewer${TEAM_ID}";
PRODUCT_NAME = "$(TARGET_NAME)";
PROVISIONING_PROFILE_SPECIFIER = "";
SWIFT_VERSION = 5.0;
TARGETED_DEVICE_FAMILY = "1,2";
};
name = Release;
};
/* End XCBuildConfiguration section */
/* Begin XCConfigurationList section */
7555FF76242A565900829871 /* Build configuration list for PBXProject "Imageviewer" */ = {
isa = XCConfigurationList;
buildConfigurations = (
7555FFA3242A565B00829871 /* Debug */,
7555FFA4242A565B00829871 /* Release */,
);
defaultConfigurationIsVisible = 0;
defaultConfigurationName = Release;
};
7555FFA5242A565B00829871 /* Build configuration list for PBXNativeTarget "Imageviewer" */ = {
isa = XCConfigurationList;
buildConfigurations = (
7555FFA6242A565B00829871 /* Debug */,
7555FFA7242A565B00829871 /* Release */,
);
defaultConfigurationIsVisible = 0;
defaultConfigurationName = Release;
};
/* End XCConfigurationList section */
};
rootObject = 7555FF73242A565900829871 /* Project object */;
}

5
experimental/examples/imageviewer/iosApp/Podfile

@ -0,0 +1,5 @@
target 'Imageviewer' do
use_frameworks!
platform :ios, '14.1'
pod 'shared', :path => '../shared'
end

48
experimental/examples/imageviewer/iosApp/iosApp/Info.plist

@ -0,0 +1,48 @@
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
<plist version="1.0">
<dict>
<key>CFBundleDevelopmentRegion</key>
<string>$(DEVELOPMENT_LANGUAGE)</string>
<key>CFBundleExecutable</key>
<string>$(EXECUTABLE_NAME)</string>
<key>CFBundleIdentifier</key>
<string>$(PRODUCT_BUNDLE_IDENTIFIER)</string>
<key>CFBundleInfoDictionaryVersion</key>
<string>6.0</string>
<key>CFBundleName</key>
<string>$(PRODUCT_NAME)</string>
<key>CFBundlePackageType</key>
<string>$(PRODUCT_BUNDLE_PACKAGE_TYPE)</string>
<key>CFBundleShortVersionString</key>
<string>1.0</string>
<key>CFBundleVersion</key>
<string>1</string>
<key>LSRequiresIPhoneOS</key>
<true/>
<key>UIApplicationSceneManifest</key>
<dict>
<key>UIApplicationSupportsMultipleScenes</key>
<false/>
</dict>
<key>UILaunchScreen</key>
<dict/>
<key>UIRequiredDeviceCapabilities</key>
<array>
<string>armv7</string>
</array>
<key>UISupportedInterfaceOrientations</key>
<array>
<string>UIInterfaceOrientationPortrait</string>
<string>UIInterfaceOrientationLandscapeLeft</string>
<string>UIInterfaceOrientationLandscapeRight</string>
</array>
<key>UISupportedInterfaceOrientations~ipad</key>
<array>
<string>UIInterfaceOrientationPortrait</string>
<string>UIInterfaceOrientationPortraitUpsideDown</string>
<string>UIInterfaceOrientationLandscapeLeft</string>
<string>UIInterfaceOrientationLandscapeRight</string>
</array>
</dict>
</plist>

15
experimental/examples/imageviewer/iosApp/iosApp/iosApp.swift

@ -0,0 +1,15 @@
import UIKit
import shared
@UIApplicationMain
class AppDelegate: UIResponder, UIApplicationDelegate {
var window: UIWindow?
func application(_ application: UIApplication, didFinishLaunchingWithOptions launchOptions: [UIApplication.LaunchOptionsKey: Any]?) -> Bool {
window = UIWindow(frame: UIScreen.main.bounds)
let mainViewController = Main_iosKt.MainViewController()
window?.rootViewController = mainViewController
window?.makeKeyAndVisible()
return true
}
}

BIN
experimental/examples/imageviewer/run-configurations.png

Binary file not shown.

After

Width:  |  Height:  |  Size: 15 KiB

8
experimental/examples/imageviewer/settings.gradle.kts

@ -12,11 +12,17 @@ pluginManagement {
kotlin("jvm").version(kotlinVersion)
kotlin("multiplatform").version(kotlinVersion)
kotlin("plugin.serialization").version(kotlinVersion)
kotlin("android").version(kotlinVersion)
id("com.android.base").version(agpVersion)
id("com.android.application").version(agpVersion)
id("com.android.library").version(agpVersion)
id("org.jetbrains.compose").version(composeVersion)
}
}
include(":common", ":android", ":desktop")
rootProject.name = "imageviewer"
include(":androidApp")
include(":shared")
include(":desktopApp")

84
experimental/examples/imageviewer/shared/build.gradle.kts

@ -0,0 +1,84 @@
plugins {
kotlin("multiplatform")
kotlin("native.cocoapods")
id("com.android.library")
id("org.jetbrains.compose")
kotlin("plugin.serialization")
}
version = "1.0-SNAPSHOT"
val ktorVersion = extra["ktor.version"]
kotlin {
android()
jvm("desktop")
ios()
iosSimulatorArm64()
cocoapods {
summary = "Shared code for the sample"
homepage = "https://github.com/JetBrains/compose-jb"
ios.deploymentTarget = "14.1"
podfile = project.file("../iosApp/Podfile")
framework {
baseName = "shared"
isStatic = true
}
extraSpecAttributes["resources"] = "['src/commonMain/resources/**', 'src/iosMain/resources/**']"
}
sourceSets {
val commonMain by getting {
dependencies {
implementation("io.ktor:ktor-client-core:$ktorVersion")
implementation(compose.runtime)
implementation(compose.foundation)
implementation(compose.material)
implementation("org.jetbrains.compose.components:components-resources:1.3.0-beta04-dev879")
implementation("org.jetbrains.kotlinx:kotlinx-serialization-json:1.4.1")
}
}
val androidMain by getting {
dependencies {
implementation("androidx.appcompat:appcompat:1.5.1")
implementation("androidx.core:core-ktx:1.9.0")
implementation("io.ktor:ktor-client-okhttp:$ktorVersion")
}
}
val iosMain by getting {
dependencies {
implementation("io.ktor:ktor-client-darwin:$ktorVersion")
}
}
val iosTest by getting
val iosSimulatorArm64Main by getting {
dependsOn(iosMain)
}
val iosSimulatorArm64Test by getting {
dependsOn(iosTest)
}
val desktopMain by getting {
dependencies {
implementation(compose.desktop.common)
implementation("org.jetbrains.kotlinx:kotlinx-coroutines-swing:1.6.4")
implementation("io.ktor:ktor-client-cio:$ktorVersion")
}
}
}
}
android {
compileSdk = 33
sourceSets["main"].manifest.srcFile("src/androidMain/AndroidManifest.xml")
sourceSets["main"].res.srcDirs("src/androidMain/res")
sourceSets["main"].resources.srcDir("src/commonMain/resources")
defaultConfig {
minSdk = 24
targetSdk = 33
}
compileOptions {
sourceCompatibility = JavaVersion.VERSION_1_8
targetCompatibility = JavaVersion.VERSION_1_8
}
}

Some files were not shown because too many files have changed in this diff Show More

Loading…
Cancel
Save