dima.avdeev
2 years ago
committed by
GitHub
53 changed files with 1460 additions and 10 deletions
@ -0,0 +1,24 @@
|
||||
# Libraries for Compose Multiplatform |
||||
|
||||
## Resources |
||||
Library to load resources, like images. |
||||
|
||||
### How to run demo project: |
||||
*Prerequisites*: to run on iOS and Android, you should have "Kotlin Multiplatform Mobile" plugin installed either |
||||
in Android Studio or in AppCode with [installed CocoaPods](https://kotlinlang.org/docs/native-cocoapods.html). |
||||
|
||||
### Run on desktop via Gradle: |
||||
`./gradlew :resources:demo:desktopApp:run` |
||||
|
||||
### Run JS in browser with WebAssembly Skia via Gradle: |
||||
`./gradlew :resources:demo:shared:jsBrowserDevelopmentRun` |
||||
|
||||
### Run MacOS via Gradle: |
||||
- on Intel CPU: `./gradlew :resources:demo:shared:runDebugExecutableMacosX64` |
||||
- on Apple Silicon: `./gradlew :resources:demo:shared:runDebugExecutableMacosArm64` |
||||
|
||||
# Tests |
||||
Run script: |
||||
```bash |
||||
./test.sh |
||||
``` |
@ -1,5 +1,5 @@
|
||||
distributionBase=GRADLE_USER_HOME |
||||
distributionPath=wrapper/dists |
||||
distributionUrl=https\://services.gradle.org/distributions/gradle-7.3.3-bin.zip |
||||
distributionUrl=https\://services.gradle.org/distributions/gradle-7.5.1-bin.zip |
||||
zipStoreBase=GRADLE_USER_HOME |
||||
zipStorePath=wrapper/dists |
||||
|
@ -0,0 +1,6 @@
|
||||
iosApp/Podfile.lock |
||||
iosApp/Pods/* |
||||
iosApp/ResourcesDemo.xcworkspace/* |
||||
iosApp/ResourcesDemo.xcodeproj/* |
||||
!iosApp/ResourcesDemo.xcodeproj/project.pbxproj |
||||
shared/shared.podspec |
@ -0,0 +1,33 @@
|
||||
plugins { |
||||
id("com.android.application") |
||||
kotlin("android") |
||||
} |
||||
|
||||
dependencies { |
||||
implementation(project(":resources:demo:shared")) |
||||
implementation("androidx.appcompat:appcompat:1.5.1") |
||||
implementation("androidx.activity:activity-compose:1.6.1") |
||||
implementation("androidx.compose.foundation:foundation:1.3.1") |
||||
implementation("androidx.compose.ui:ui:1.3.1") |
||||
} |
||||
|
||||
android { |
||||
buildFeatures { |
||||
compose = true |
||||
} |
||||
composeOptions { |
||||
kotlinCompilerExtensionVersion = "1.3.2" |
||||
} |
||||
compileSdk = 33 |
||||
defaultConfig { |
||||
applicationId = "me.user.androidApp" |
||||
minSdk = 24 |
||||
targetSdk = 33 |
||||
versionCode = 1 |
||||
versionName = "1.0" |
||||
} |
||||
compileOptions { |
||||
sourceCompatibility = JavaVersion.VERSION_1_8 |
||||
targetCompatibility = JavaVersion.VERSION_1_8 |
||||
} |
||||
} |
@ -0,0 +1,20 @@
|
||||
<?xml version="1.0" encoding="utf-8"?> |
||||
<manifest xmlns:android="http://schemas.android.com/apk/res/android" |
||||
package="org.jetbrains.compose.resources.demo"> |
||||
|
||||
<application |
||||
android:allowBackup="true" |
||||
android:label="@string/app_name" |
||||
android:supportsRtl="true" |
||||
android:theme="@style/Theme.AppCompat.Light.NoActionBar"> |
||||
<activity |
||||
android:exported="true" |
||||
android:name=".MainActivity"> |
||||
<intent-filter> |
||||
<action android:name="android.intent.action.MAIN"/> |
||||
<category android:name="android.intent.category.LAUNCHER"/> |
||||
</intent-filter> |
||||
</activity> |
||||
</application> |
||||
|
||||
</manifest> |
@ -0,0 +1,15 @@
|
||||
package org.jetbrains.compose.resources.demo |
||||
|
||||
import android.os.Bundle |
||||
import androidx.activity.compose.setContent |
||||
import androidx.appcompat.app.AppCompatActivity |
||||
import org.jetbrains.compose.resources.demo.shared.MainView |
||||
|
||||
class MainActivity : AppCompatActivity() { |
||||
override fun onCreate(savedInstanceState: Bundle?) { |
||||
super.onCreate(savedInstanceState) |
||||
setContent { |
||||
MainView() |
||||
} |
||||
} |
||||
} |
@ -0,0 +1,3 @@
|
||||
<resources> |
||||
<string name="app_name">Resources demo</string> |
||||
</resources> |
@ -0,0 +1,22 @@
|
||||
plugins { |
||||
kotlin("multiplatform") |
||||
id("org.jetbrains.compose") |
||||
} |
||||
|
||||
kotlin { |
||||
jvm {} |
||||
sourceSets { |
||||
val jvmMain by getting { |
||||
dependencies { |
||||
implementation(compose.desktop.currentOs) |
||||
implementation(project(":resources:demo:shared")) |
||||
} |
||||
} |
||||
} |
||||
} |
||||
|
||||
compose.desktop { |
||||
application { |
||||
mainClass = "MainKt" |
||||
} |
||||
} |
@ -0,0 +1,18 @@
|
||||
/* |
||||
* Copyright 2020-2022 JetBrains s.r.o. and respective authors and developers. |
||||
* Use of this source code is governed by the Apache 2.0 license that can be found in the LICENSE.txt file. |
||||
*/ |
||||
|
||||
import androidx.compose.ui.unit.DpSize |
||||
import androidx.compose.ui.unit.dp |
||||
import androidx.compose.ui.window.WindowState |
||||
import androidx.compose.ui.window.singleWindowApplication |
||||
import org.jetbrains.compose.resources.demo.shared.MainView |
||||
|
||||
fun main() = |
||||
singleWindowApplication( |
||||
title = "Resources demo", |
||||
state = WindowState(size = DpSize(800.dp, 800.dp)) |
||||
) { |
||||
MainView() |
||||
} |
@ -0,0 +1 @@
|
||||
TEAM_ID= |
@ -0,0 +1,5 @@
|
||||
target 'ResourcesDemo' do |
||||
use_frameworks! |
||||
platform :ios, '14.1' |
||||
pod 'shared', :path => '../shared' |
||||
end |
@ -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_ResourcesDemo.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 8DE96E47030356CE6AD9794A /* Pods_ResourcesDemo.framework */; }; |
||||
/* End PBXBuildFile section */ |
||||
|
||||
/* Begin PBXFileReference section */ |
||||
1EB65E27D2C0F884D0A1A133 /* Pods-ResourcesDemo.debug.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-ResourcesDemo.debug.xcconfig"; path = "Target Support Files/Pods-ResourcesDemo/Pods-ResourcesDemo.debug.xcconfig"; sourceTree = "<group>"; }; |
||||
2152FB032600AC8F00CF470E /* iosApp.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = iosApp.swift; sourceTree = "<group>"; }; |
||||
3D7A606AB0AD7636269BD9D0 /* Pods-ResourcesDemo.release.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-ResourcesDemo.release.xcconfig"; path = "Target Support Files/Pods-ResourcesDemo/Pods-ResourcesDemo.release.xcconfig"; sourceTree = "<group>"; }; |
||||
7555FF7B242A565900829871 /* ResourcesDemo.app */ = {isa = PBXFileReference; explicitFileType = wrapper.application; includeInIndex = 0; path = ResourcesDemo.app; sourceTree = BUILT_PRODUCTS_DIR; }; |
||||
7555FF8C242A565B00829871 /* Info.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist.xml; path = Info.plist; sourceTree = "<group>"; }; |
||||
8DE96E47030356CE6AD9794A /* Pods_ResourcesDemo.framework */ = {isa = PBXFileReference; explicitFileType = wrapper.framework; includeInIndex = 0; path = Pods_ResourcesDemo.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_ResourcesDemo.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 /* ResourcesDemo.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_ResourcesDemo.framework */, |
||||
); |
||||
name = Frameworks; |
||||
sourceTree = "<group>"; |
||||
}; |
||||
E1DAFBE8E1CFC0878361EF0E /* Pods */ = { |
||||
isa = PBXGroup; |
||||
children = ( |
||||
1EB65E27D2C0F884D0A1A133 /* Pods-ResourcesDemo.debug.xcconfig */, |
||||
3D7A606AB0AD7636269BD9D0 /* Pods-ResourcesDemo.release.xcconfig */, |
||||
); |
||||
path = Pods; |
||||
sourceTree = "<group>"; |
||||
}; |
||||
/* End PBXGroup section */ |
||||
|
||||
/* Begin PBXNativeTarget section */ |
||||
7555FF7A242A565900829871 /* ResourcesDemo */ = { |
||||
isa = PBXNativeTarget; |
||||
buildConfigurationList = 7555FFA5242A565B00829871 /* Build configuration list for PBXNativeTarget "ResourcesDemo" */; |
||||
buildPhases = ( |
||||
E8D673591E7196AEA2EA10E2 /* [CP] Check Pods Manifest.lock */, |
||||
7555FF77242A565900829871 /* Sources */, |
||||
7555FF79242A565900829871 /* Resources */, |
||||
9964867F0862B4D9FB6ABFC7 /* Frameworks */, |
||||
A51DDDB74597C98E89765935 /* [CP] Copy Pods Resources */, |
||||
); |
||||
buildRules = ( |
||||
); |
||||
dependencies = ( |
||||
); |
||||
name = ResourcesDemo; |
||||
productName = iosApp; |
||||
productReference = 7555FF7B242A565900829871 /* ResourcesDemo.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 "ResourcesDemo" */; |
||||
compatibilityVersion = "Xcode 9.3"; |
||||
developmentRegion = en; |
||||
hasScannedForEncodings = 0; |
||||
knownRegions = ( |
||||
en, |
||||
Base, |
||||
); |
||||
mainGroup = 7555FF72242A565900829871; |
||||
productRefGroup = 7555FF7C242A565900829871 /* Products */; |
||||
projectDirPath = ""; |
||||
projectRoot = ""; |
||||
targets = ( |
||||
7555FF7A242A565900829871 /* ResourcesDemo */, |
||||
); |
||||
}; |
||||
/* End PBXProject section */ |
||||
|
||||
/* Begin PBXResourcesBuildPhase section */ |
||||
7555FF79242A565900829871 /* Resources */ = { |
||||
isa = PBXResourcesBuildPhase; |
||||
buildActionMask = 2147483647; |
||||
files = ( |
||||
); |
||||
runOnlyForDeploymentPostprocessing = 0; |
||||
}; |
||||
/* End PBXResourcesBuildPhase section */ |
||||
|
||||
/* Begin PBXShellScriptBuildPhase section */ |
||||
A51DDDB74597C98E89765935 /* [CP] Copy Pods Resources */ = { |
||||
isa = PBXShellScriptBuildPhase; |
||||
buildActionMask = 2147483647; |
||||
files = ( |
||||
); |
||||
inputFileListPaths = ( |
||||
"${PODS_ROOT}/Target Support Files/Pods-ResourcesDemo/Pods-ResourcesDemo-resources-${CONFIGURATION}-input-files.xcfilelist", |
||||
); |
||||
name = "[CP] Copy Pods Resources"; |
||||
outputFileListPaths = ( |
||||
"${PODS_ROOT}/Target Support Files/Pods-ResourcesDemo/Pods-ResourcesDemo-resources-${CONFIGURATION}-output-files.xcfilelist", |
||||
); |
||||
runOnlyForDeploymentPostprocessing = 0; |
||||
shellPath = /bin/sh; |
||||
shellScript = "\"${PODS_ROOT}/Target Support Files/Pods-ResourcesDemo/Pods-ResourcesDemo-resources.sh\"\n"; |
||||
showEnvVarsInLog = 0; |
||||
}; |
||||
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-ResourcesDemo-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; |
||||
}; |
||||
/* 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-ResourcesDemo.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.ResourcesDemo${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-ResourcesDemo.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.ResourcesDemo${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 "ResourcesDemo" */ = { |
||||
isa = XCConfigurationList; |
||||
buildConfigurations = ( |
||||
7555FFA3242A565B00829871 /* Debug */, |
||||
7555FFA4242A565B00829871 /* Release */, |
||||
); |
||||
defaultConfigurationIsVisible = 0; |
||||
defaultConfigurationName = Release; |
||||
}; |
||||
7555FFA5242A565B00829871 /* Build configuration list for PBXNativeTarget "ResourcesDemo" */ = { |
||||
isa = XCConfigurationList; |
||||
buildConfigurations = ( |
||||
7555FFA6242A565B00829871 /* Debug */, |
||||
7555FFA7242A565B00829871 /* Release */, |
||||
); |
||||
defaultConfigurationIsVisible = 0; |
||||
defaultConfigurationName = Release; |
||||
}; |
||||
/* End XCConfigurationList section */ |
||||
}; |
||||
rootObject = 7555FF73242A565900829871 /* Project object */; |
||||
} |
@ -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> |
@ -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 |
||||
} |
||||
} |
@ -0,0 +1,101 @@
|
||||
plugins { |
||||
kotlin("multiplatform") |
||||
kotlin("native.cocoapods") |
||||
id("com.android.library") |
||||
id("org.jetbrains.compose") |
||||
} |
||||
|
||||
version = "1.0-SNAPSHOT" |
||||
|
||||
kotlin { |
||||
android() |
||||
jvm("desktop") |
||||
ios() |
||||
iosSimulatorArm64() |
||||
js(IR) { |
||||
browser() |
||||
binaries.executable() |
||||
} |
||||
macosX64 { |
||||
binaries { |
||||
executable { |
||||
entryPoint = "main" |
||||
} |
||||
} |
||||
} |
||||
macosArm64 { |
||||
binaries { |
||||
executable { |
||||
entryPoint = "main" |
||||
} |
||||
} |
||||
} |
||||
|
||||
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(compose.ui) |
||||
implementation(compose.foundation) |
||||
implementation(compose.material) |
||||
implementation(compose.runtime) |
||||
implementation(project(":resources:library")) |
||||
} |
||||
} |
||||
val iosMain by getting |
||||
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) |
||||
} |
||||
} |
||||
val macosMain by creating { |
||||
dependsOn(commonMain) |
||||
} |
||||
val macosX64Main by getting { |
||||
dependsOn(macosMain) |
||||
} |
||||
val macosArm64Main by getting { |
||||
dependsOn(macosMain) |
||||
} |
||||
} |
||||
} |
||||
|
||||
android { |
||||
compileSdk = 33 |
||||
sourceSets["main"].manifest.srcFile("src/androidMain/AndroidManifest.xml") |
||||
defaultConfig { |
||||
minSdk = 24 |
||||
targetSdk = 33 |
||||
} |
||||
compileOptions { |
||||
sourceCompatibility = JavaVersion.VERSION_1_8 |
||||
targetCompatibility = JavaVersion.VERSION_1_8 |
||||
} |
||||
sourceSets { |
||||
named("main") { |
||||
resources.srcDir("src/commonMain/resources") |
||||
} |
||||
} |
||||
} |
||||
|
||||
compose.experimental { |
||||
web.application {} |
||||
} |
@ -0,0 +1,2 @@
|
||||
<?xml version="1.0" encoding="utf-8"?> |
||||
<manifest package="org.jetbrains.compose.resources.demo.shared"/> |
@ -0,0 +1,13 @@
|
||||
/* |
||||
* Copyright 2020-2022 JetBrains s.r.o. and respective authors and developers. |
||||
* Use of this source code is governed by the Apache 2.0 license that can be found in the LICENSE.txt file. |
||||
*/ |
||||
|
||||
package org.jetbrains.compose.resources.demo.shared |
||||
|
||||
import androidx.compose.runtime.Composable |
||||
|
||||
@Composable |
||||
fun MainView() { |
||||
UseResources() |
||||
} |
@ -0,0 +1,23 @@
|
||||
package org.jetbrains.compose.resources.demo.shared |
||||
|
||||
import androidx.compose.foundation.Image |
||||
import androidx.compose.foundation.layout.* |
||||
import androidx.compose.material.Text |
||||
import androidx.compose.runtime.Composable |
||||
import org.jetbrains.compose.resources.* |
||||
|
||||
@OptIn(ExperimentalResourceApi::class) |
||||
@Composable |
||||
internal fun UseResources() { |
||||
Column { |
||||
Text("Hello, resources") |
||||
Image( |
||||
bitmap = resource("dir/img.png").rememberImageBitmap().orEmpty(), |
||||
contentDescription = null, |
||||
) |
||||
Image( |
||||
bitmap = resource("img.webp").rememberImageBitmap().orEmpty(), |
||||
contentDescription = null, |
||||
) |
||||
} |
||||
} |
After Width: | Height: | Size: 31 KiB |
After Width: | Height: | Size: 10 KiB |
@ -0,0 +1,20 @@
|
||||
/* |
||||
* Copyright 2020-2022 JetBrains s.r.o. and respective authors and developers. |
||||
* Use of this source code is governed by the Apache 2.0 license that can be found in the LICENSE.txt file. |
||||
*/ |
||||
|
||||
package org.jetbrains.compose.resources.demo.shared |
||||
|
||||
import androidx.compose.desktop.ui.tooling.preview.Preview |
||||
import androidx.compose.runtime.Composable |
||||
|
||||
@Composable |
||||
fun MainView() { |
||||
UseResources() |
||||
} |
||||
|
||||
@Preview |
||||
@Composable |
||||
fun Preview() { |
||||
MainView() |
||||
} |
@ -0,0 +1,24 @@
|
||||
/* |
||||
* Copyright 2020-2022 JetBrains s.r.o. and respective authors and developers. |
||||
* Use of this source code is governed by the Apache 2.0 license that can be found in the LICENSE.txt file. |
||||
*/ |
||||
|
||||
import androidx.compose.foundation.layout.Box |
||||
import androidx.compose.foundation.layout.Column |
||||
import androidx.compose.foundation.layout.height |
||||
import androidx.compose.ui.Modifier |
||||
import androidx.compose.ui.unit.dp |
||||
import androidx.compose.ui.window.Application |
||||
import org.jetbrains.compose.resources.demo.shared.UseResources |
||||
import platform.UIKit.UIViewController |
||||
|
||||
fun MainViewController(): UIViewController = |
||||
Application("Resources demo") { |
||||
Column { |
||||
Box( |
||||
modifier = Modifier |
||||
.height(100.dp) |
||||
) |
||||
UseResources() |
||||
} |
||||
} |
@ -0,0 +1,31 @@
|
||||
/* |
||||
* Copyright 2020-2022 JetBrains s.r.o. and respective authors and developers. |
||||
* Use of this source code is governed by the Apache 2.0 license that can be found in the LICENSE.txt file. |
||||
*/ |
||||
|
||||
import androidx.compose.foundation.layout.Column |
||||
import androidx.compose.foundation.layout.Spacer |
||||
import androidx.compose.foundation.layout.fillMaxSize |
||||
import androidx.compose.foundation.layout.height |
||||
import androidx.compose.runtime.Composable |
||||
import androidx.compose.ui.Modifier |
||||
import androidx.compose.ui.unit.dp |
||||
import androidx.compose.ui.window.Window |
||||
import org.jetbrains.compose.resources.demo.shared.UseResources |
||||
import org.jetbrains.skiko.wasm.onWasmReady |
||||
|
||||
fun main() { |
||||
onWasmReady { |
||||
Window("Resources demo") { |
||||
MainView() |
||||
} |
||||
} |
||||
} |
||||
|
||||
@Composable |
||||
fun MainView() { |
||||
Column(modifier = Modifier.fillMaxSize()) { |
||||
Spacer(modifier = Modifier.height(24.dp)) |
||||
UseResources() |
||||
} |
||||
} |
@ -0,0 +1,16 @@
|
||||
<!DOCTYPE html> |
||||
<html lang="en"> |
||||
<head> |
||||
<meta charset="UTF-8"> |
||||
<title>compose multiplatform web demo</title> |
||||
<script src="skiko.js"> </script> |
||||
<link type="text/css" rel="stylesheet" href="styles.css" /> |
||||
</head> |
||||
<body> |
||||
<h1>compose multiplatform web demo</h1> |
||||
<div> |
||||
<canvas id="ComposeTarget" width="800" height="600"></canvas> |
||||
</div> |
||||
<script src="shared.js"> </script> |
||||
</body> |
||||
</html> |
@ -0,0 +1,8 @@
|
||||
#root { |
||||
width: 100%; |
||||
height: 100vh; |
||||
} |
||||
|
||||
#root > .compose-web-column > div { |
||||
position: relative; |
||||
} |
@ -0,0 +1,17 @@
|
||||
/* |
||||
* Copyright 2020-2022 JetBrains s.r.o. and respective authors and developers. |
||||
* Use of this source code is governed by the Apache 2.0 license that can be found in the LICENSE.txt file. |
||||
*/ |
||||
|
||||
import androidx.compose.ui.window.Window |
||||
import org.jetbrains.compose.resources.demo.shared.UseResources |
||||
import platform.AppKit.NSApp |
||||
import platform.AppKit.NSApplication |
||||
|
||||
fun main() { |
||||
NSApplication.sharedApplication() |
||||
Window("Resources demo") { |
||||
UseResources() |
||||
} |
||||
NSApp?.run() |
||||
} |
@ -0,0 +1,61 @@
|
||||
package org.jetbrains.compose.resources |
||||
|
||||
import androidx.compose.foundation.Image |
||||
import androidx.compose.runtime.Composable |
||||
import androidx.compose.runtime.collectAsState |
||||
import androidx.compose.runtime.getValue |
||||
import androidx.compose.ui.graphics.ImageBitmap |
||||
import androidx.compose.ui.test.junit4.createComposeRule |
||||
import kotlinx.coroutines.ExperimentalCoroutinesApi |
||||
import kotlinx.coroutines.delay |
||||
import kotlinx.coroutines.flow.MutableStateFlow |
||||
import kotlinx.coroutines.test.UnconfinedTestDispatcher |
||||
import kotlinx.coroutines.test.runTest |
||||
import org.junit.Assert.assertEquals |
||||
import org.junit.Rule |
||||
import org.junit.Test |
||||
|
||||
@OptIn(ExperimentalResourceApi::class, ExperimentalCoroutinesApi::class) |
||||
class ComposeResourceTest { |
||||
|
||||
@get:Rule |
||||
val rule = createComposeRule() |
||||
|
||||
@Test |
||||
fun testMissingResource() = runTest (UnconfinedTestDispatcher()) { |
||||
var recompositionCount = 0 |
||||
rule.setContent { |
||||
CountRecompositions(resource("missing.png").rememberImageBitmap().orEmpty()) { |
||||
recompositionCount++ |
||||
} |
||||
} |
||||
rule.awaitIdle() |
||||
assertEquals(2, recompositionCount) |
||||
} |
||||
|
||||
@Test |
||||
fun testCountRecompositions() = runTest (UnconfinedTestDispatcher()) { |
||||
val mutableStateFlow = MutableStateFlow(true) |
||||
var recompositionCount = 0 |
||||
rule.setContent { |
||||
val state: Boolean by mutableStateFlow.collectAsState(true) |
||||
val resource = resource(if (state) "1.png" else "2.png") |
||||
CountRecompositions(resource.rememberImageBitmap().orEmpty()) { |
||||
recompositionCount++ |
||||
} |
||||
} |
||||
rule.awaitIdle() |
||||
mutableStateFlow.value = false |
||||
rule.awaitIdle() |
||||
assertEquals(4, recompositionCount) |
||||
} |
||||
|
||||
} |
||||
|
||||
@Composable |
||||
private fun CountRecompositions(imageBitmap: ImageBitmap?, onRecomposition: () -> Unit) { |
||||
onRecomposition() |
||||
if (imageBitmap != null) { |
||||
Image(bitmap = imageBitmap, contentDescription = null) |
||||
} |
||||
} |
After Width: | Height: | Size: 946 B |
After Width: | Height: | Size: 785 B |
@ -0,0 +1,2 @@
|
||||
<?xml version="1.0" encoding="utf-8"?> |
||||
<manifest package="org.jetbrains.compose.components.resources"/> |
@ -0,0 +1,18 @@
|
||||
/* |
||||
* Copyright 2020-2022 JetBrains s.r.o. and respective authors and developers. |
||||
* Use of this source code is governed by the Apache 2.0 license that can be found in the LICENSE.txt file. |
||||
*/ |
||||
|
||||
package org.jetbrains.compose.resources |
||||
|
||||
import android.graphics.Bitmap |
||||
import android.graphics.BitmapFactory |
||||
import androidx.compose.ui.graphics.ImageBitmap |
||||
import androidx.compose.ui.graphics.asImageBitmap |
||||
|
||||
@ExperimentalResourceApi |
||||
internal actual fun ByteArray.toImageBitmap(): ImageBitmap = toAndroidBitmap().asImageBitmap() |
||||
|
||||
private fun ByteArray.toAndroidBitmap(): Bitmap { |
||||
return BitmapFactory.decodeByteArray(this, 0, size); |
||||
} |
@ -0,0 +1,26 @@
|
||||
/* |
||||
* Copyright 2020-2022 JetBrains s.r.o. and respective authors and developers. |
||||
* Use of this source code is governed by the Apache 2.0 license that can be found in the LICENSE.txt file. |
||||
*/ |
||||
|
||||
package org.jetbrains.compose.resources |
||||
|
||||
import java.io.IOException |
||||
|
||||
@ExperimentalResourceApi |
||||
actual fun resource(path: String): Resource = AndroidResourceImpl(path) |
||||
|
||||
@ExperimentalResourceApi |
||||
private class AndroidResourceImpl(path: String) : AbstractResourceImpl(path) { |
||||
override suspend fun readBytes(): ByteArray { |
||||
val resource = (::AndroidResourceImpl.javaClass.classLoader).getResourceAsStream(path) |
||||
if (resource != null) { |
||||
return resource.readBytes() |
||||
} else { |
||||
throw MissingResourceException(path) |
||||
} |
||||
} |
||||
} |
||||
|
||||
internal actual class MissingResourceException actual constructor(path: String) : |
||||
IOException("Missing resource with path: $path") |
@ -0,0 +1,40 @@
|
||||
/* |
||||
* Copyright 2020-2022 JetBrains s.r.o. and respective authors and developers. |
||||
* Use of this source code is governed by the Apache 2.0 license that can be found in the LICENSE.txt file. |
||||
*/ |
||||
|
||||
package org.jetbrains.compose.resources |
||||
|
||||
import androidx.compose.runtime.* |
||||
import androidx.compose.ui.graphics.ImageBitmap |
||||
|
||||
private val emptyImageBitmap: ImageBitmap by lazy { ImageBitmap(1, 1) } |
||||
|
||||
/** |
||||
* Get and remember resource. While loading and if resource not exists result will be null. |
||||
*/ |
||||
@ExperimentalResourceApi |
||||
@Composable |
||||
fun Resource.rememberImageBitmap(): LoadState<ImageBitmap> { |
||||
val state: MutableState<LoadState<ImageBitmap>> = remember(this) { mutableStateOf(LoadState.Loading()) } |
||||
LaunchedEffect(this) { |
||||
state.value = try { |
||||
LoadState.Success(readBytes().toImageBitmap()) |
||||
} catch (e: Exception) { |
||||
LoadState.Error(e) |
||||
} |
||||
} |
||||
return state.value |
||||
} |
||||
|
||||
/** |
||||
* return current ImageBitmap or return empty while loading |
||||
*/ |
||||
@ExperimentalResourceApi |
||||
fun LoadState<ImageBitmap>.orEmpty(): ImageBitmap = when (this) { |
||||
is LoadState.Loading -> emptyImageBitmap |
||||
is LoadState.Success -> this.value |
||||
is LoadState.Error -> emptyImageBitmap |
||||
} |
||||
|
||||
internal expect fun ByteArray.toImageBitmap(): ImageBitmap |
@ -0,0 +1,9 @@
|
||||
/* |
||||
* Copyright 2020-2022 JetBrains s.r.o. and respective authors and developers. |
||||
* Use of this source code is governed by the Apache 2.0 license that can be found in the LICENSE.txt file. |
||||
*/ |
||||
|
||||
package org.jetbrains.compose.resources |
||||
|
||||
@RequiresOptIn("This API is experimental and is likely to change in the future.") |
||||
annotation class ExperimentalResourceApi |
@ -0,0 +1,38 @@
|
||||
/* |
||||
* Copyright 2020-2022 JetBrains s.r.o. and respective authors and developers. |
||||
* Use of this source code is governed by the Apache 2.0 license that can be found in the LICENSE.txt file. |
||||
*/ |
||||
|
||||
package org.jetbrains.compose.resources |
||||
|
||||
/** |
||||
* Should implement equals() and hashCode() |
||||
*/ |
||||
@ExperimentalResourceApi |
||||
interface Resource { |
||||
suspend fun readBytes(): ByteArray //todo in future use streaming |
||||
} |
||||
|
||||
/** |
||||
* Get a resource from <sourceSet>/resources (for example, from commonMain/resources). |
||||
*/ |
||||
@ExperimentalResourceApi |
||||
expect fun resource(path: String): Resource |
||||
|
||||
internal expect class MissingResourceException(path: String) |
||||
|
||||
@OptIn(ExperimentalResourceApi::class) |
||||
internal abstract class AbstractResourceImpl(val path: String) : Resource { |
||||
override fun equals(other: Any?): Boolean { |
||||
if (this === other) return true |
||||
return if (other is AbstractResourceImpl) { |
||||
path == other.path |
||||
} else { |
||||
false |
||||
} |
||||
} |
||||
|
||||
override fun hashCode(): Int { |
||||
return path.hashCode() |
||||
} |
||||
} |
@ -0,0 +1,23 @@
|
||||
/* |
||||
* Copyright 2020-2022 JetBrains s.r.o. and respective authors and developers. |
||||
* Use of this source code is governed by the Apache 2.0 license that can be found in the LICENSE.txt file. |
||||
*/ |
||||
|
||||
package org.jetbrains.compose.resources |
||||
|
||||
import kotlin.test.Test |
||||
import kotlin.test.assertEquals |
||||
import kotlin.test.assertNotEquals |
||||
|
||||
@OptIn(ExperimentalResourceApi::class) |
||||
class ResourceTest { |
||||
@Test |
||||
fun testResourceEquals() { |
||||
assertEquals(resource("a"), resource("a")) |
||||
} |
||||
|
||||
@Test |
||||
fun testResourceNotEquals() { |
||||
assertNotEquals(resource("a"), resource("b")) |
||||
} |
||||
} |
@ -0,0 +1,26 @@
|
||||
/* |
||||
* Copyright 2020-2022 JetBrains s.r.o. and respective authors and developers. |
||||
* Use of this source code is governed by the Apache 2.0 license that can be found in the LICENSE.txt file. |
||||
*/ |
||||
|
||||
package org.jetbrains.compose.resources |
||||
|
||||
import java.io.IOException |
||||
|
||||
@ExperimentalResourceApi |
||||
actual fun resource(path: String): Resource = DesktopResourceImpl(path) |
||||
|
||||
@ExperimentalResourceApi |
||||
private class DesktopResourceImpl(path: String) : AbstractResourceImpl(path) { |
||||
override suspend fun readBytes(): ByteArray { |
||||
val resource = (::DesktopResourceImpl.javaClass.classLoader).getResourceAsStream(path) |
||||
if (resource != null) { |
||||
return resource.readBytes() |
||||
} else { |
||||
throw MissingResourceException(path) |
||||
} |
||||
} |
||||
} |
||||
|
||||
internal actual class MissingResourceException actual constructor(path: String) : |
||||
IOException("Missing resource with path: $path") |
@ -0,0 +1,61 @@
|
||||
package org.jetbrains.compose.resources |
||||
|
||||
import androidx.compose.foundation.Image |
||||
import androidx.compose.runtime.Composable |
||||
import androidx.compose.runtime.collectAsState |
||||
import androidx.compose.runtime.getValue |
||||
import androidx.compose.ui.graphics.ImageBitmap |
||||
import androidx.compose.ui.test.junit4.createComposeRule |
||||
import kotlinx.coroutines.ExperimentalCoroutinesApi |
||||
import kotlinx.coroutines.delay |
||||
import kotlinx.coroutines.flow.MutableStateFlow |
||||
import kotlinx.coroutines.test.UnconfinedTestDispatcher |
||||
import kotlinx.coroutines.test.runTest |
||||
import org.junit.Assert.assertEquals |
||||
import org.junit.Rule |
||||
import org.junit.Test |
||||
|
||||
@OptIn(ExperimentalResourceApi::class, ExperimentalCoroutinesApi::class) |
||||
class ComposeResourceTest { |
||||
|
||||
@get:Rule |
||||
val rule = createComposeRule() |
||||
|
||||
@Test |
||||
fun testMissingResource() = runTest (UnconfinedTestDispatcher()) { |
||||
var recompositionCount = 0 |
||||
rule.setContent { |
||||
CountRecompositions(resource("missing.png").rememberImageBitmap().orEmpty()) { |
||||
recompositionCount++ |
||||
} |
||||
} |
||||
rule.awaitIdle() |
||||
assertEquals(2, recompositionCount) |
||||
} |
||||
|
||||
@Test |
||||
fun testCountRecompositions() = runTest (UnconfinedTestDispatcher()) { |
||||
val mutableStateFlow = MutableStateFlow(true) |
||||
var recompositionCount = 0 |
||||
rule.setContent { |
||||
val state: Boolean by mutableStateFlow.collectAsState(true) |
||||
val resource = resource(if (state) "1.png" else "2.png") |
||||
CountRecompositions(resource.rememberImageBitmap().orEmpty()) { |
||||
recompositionCount++ |
||||
} |
||||
} |
||||
rule.awaitIdle() |
||||
mutableStateFlow.value = false |
||||
rule.awaitIdle() |
||||
assertEquals(4, recompositionCount) |
||||
} |
||||
|
||||
} |
||||
|
||||
@Composable |
||||
private fun CountRecompositions(imageBitmap: ImageBitmap?, onRecomposition: () -> Unit) { |
||||
onRecomposition() |
||||
if (imageBitmap != null) { |
||||
Image(bitmap = imageBitmap, contentDescription = null) |
||||
} |
||||
} |
After Width: | Height: | Size: 946 B |
After Width: | Height: | Size: 785 B |
@ -0,0 +1,37 @@
|
||||
/* |
||||
* Copyright 2020-2022 JetBrains s.r.o. and respective authors and developers. |
||||
* Use of this source code is governed by the Apache 2.0 license that can be found in the LICENSE.txt file. |
||||
*/ |
||||
|
||||
package org.jetbrains.compose.resources |
||||
|
||||
|
||||
import kotlinx.cinterop.addressOf |
||||
import kotlinx.cinterop.usePinned |
||||
import platform.Foundation.NSBundle |
||||
import platform.Foundation.NSData |
||||
import platform.Foundation.NSFileManager |
||||
import platform.posix.memcpy |
||||
|
||||
@ExperimentalResourceApi |
||||
actual fun resource(path: String): Resource = UIKitResourceImpl(path) |
||||
|
||||
@ExperimentalResourceApi |
||||
private class UIKitResourceImpl(path: String) : AbstractResourceImpl(path) { |
||||
override suspend fun readBytes(): ByteArray { |
||||
val absolutePath = NSBundle.mainBundle.resourcePath + "/" + path |
||||
val contentsAtPath: NSData? = NSFileManager.defaultManager().contentsAtPath(absolutePath) |
||||
if (contentsAtPath != null) { |
||||
val byteArray = ByteArray(contentsAtPath.length.toInt()) |
||||
byteArray.usePinned { |
||||
memcpy(it.addressOf(0), contentsAtPath.bytes, contentsAtPath.length) |
||||
} |
||||
return byteArray |
||||
} else { |
||||
throw MissingResourceException(path) |
||||
} |
||||
} |
||||
} |
||||
|
||||
internal actual class MissingResourceException actual constructor(path: String) : |
||||
Exception("Missing resource with path: $path") |
@ -0,0 +1,44 @@
|
||||
/* |
||||
* Copyright 2020-2022 JetBrains s.r.o. and respective authors and developers. |
||||
* Use of this source code is governed by the Apache 2.0 license that can be found in the LICENSE.txt file. |
||||
*/ |
||||
|
||||
package org.jetbrains.compose.resources |
||||
|
||||
import org.khronos.webgl.ArrayBuffer |
||||
import org.khronos.webgl.Int8Array |
||||
import org.w3c.xhr.ARRAYBUFFER |
||||
import org.w3c.xhr.XMLHttpRequest |
||||
import org.w3c.xhr.XMLHttpRequestResponseType |
||||
import kotlin.coroutines.resume |
||||
import kotlin.coroutines.resumeWithException |
||||
import kotlin.coroutines.suspendCoroutine |
||||
|
||||
@ExperimentalResourceApi |
||||
actual fun resource(path: String): Resource = JSResourceImpl(path) |
||||
|
||||
@ExperimentalResourceApi |
||||
private class JSResourceImpl(path: String) : AbstractResourceImpl(path) { |
||||
override suspend fun readBytes(): ByteArray { |
||||
return suspendCoroutine { continuation -> |
||||
val req = XMLHttpRequest() |
||||
req.open("GET", "/$path", true) |
||||
req.responseType = XMLHttpRequestResponseType.ARRAYBUFFER |
||||
|
||||
req.onload = { event -> |
||||
val arrayBuffer = req.response |
||||
if (arrayBuffer is ArrayBuffer) { |
||||
continuation.resume(arrayBuffer.toByteArray()) |
||||
} else { |
||||
continuation.resumeWithException(MissingResourceException(path)) |
||||
} |
||||
} |
||||
req.send(null) |
||||
} |
||||
} |
||||
} |
||||
|
||||
private fun ArrayBuffer.toByteArray() = Int8Array(this, 0, byteLength).unsafeCast<ByteArray>() |
||||
|
||||
internal actual class MissingResourceException actual constructor(path: String) : |
||||
Exception("Missing resource with path: $path") |
@ -0,0 +1,39 @@
|
||||
/* |
||||
* Copyright 2020-2022 JetBrains s.r.o. and respective authors and developers. |
||||
* Use of this source code is governed by the Apache 2.0 license that can be found in the LICENSE.txt file. |
||||
*/ |
||||
|
||||
package org.jetbrains.compose.resources |
||||
|
||||
import kotlinx.cinterop.addressOf |
||||
import kotlinx.cinterop.usePinned |
||||
import platform.Foundation.NSData |
||||
import platform.Foundation.NSFileManager |
||||
import platform.posix.memcpy |
||||
|
||||
@ExperimentalResourceApi |
||||
actual fun resource(path: String): Resource = MacOSResourceImpl(path) |
||||
|
||||
@ExperimentalResourceApi |
||||
private class MacOSResourceImpl(path: String) : AbstractResourceImpl(path) { |
||||
override suspend fun readBytes(): ByteArray { |
||||
val currentDirectoryPath = NSFileManager.defaultManager().currentDirectoryPath |
||||
val contentsAtPath: NSData? = NSFileManager.defaultManager().run { |
||||
//todo in future bundle resources with app and use all sourceSets (skikoMain, nativeMain) |
||||
contentsAtPath("$currentDirectoryPath/src/macosMain/resources/$path") |
||||
?: contentsAtPath("$currentDirectoryPath/src/commonMain/resources/$path") |
||||
} |
||||
if (contentsAtPath != null) { |
||||
val byteArray = ByteArray(contentsAtPath.length.toInt()) |
||||
byteArray.usePinned { |
||||
memcpy(it.addressOf(0), contentsAtPath.bytes, contentsAtPath.length) |
||||
} |
||||
return byteArray |
||||
} else { |
||||
throw MissingResourceException(path) |
||||
} |
||||
} |
||||
} |
||||
|
||||
internal actual class MissingResourceException actual constructor(path: String) : |
||||
Exception("Missing resource with path: $path") |
@ -0,0 +1,13 @@
|
||||
/* |
||||
* Copyright 2020-2022 JetBrains s.r.o. and respective authors and developers. |
||||
* Use of this source code is governed by the Apache 2.0 license that can be found in the LICENSE.txt file. |
||||
*/ |
||||
|
||||
package org.jetbrains.compose.resources |
||||
|
||||
import androidx.compose.ui.graphics.ImageBitmap |
||||
import androidx.compose.ui.graphics.toComposeImageBitmap |
||||
import org.jetbrains.skia.Image |
||||
|
||||
internal actual fun ByteArray.toImageBitmap(): ImageBitmap = |
||||
Image.makeFromEncoded(this).toComposeImageBitmap() |
@ -0,0 +1,11 @@
|
||||
#!/bin/bash |
||||
cd "$(dirname "$0")" # Run always in current dir |
||||
set -euo pipefail # Fail fast |
||||
|
||||
# Unit tests |
||||
./gradlew :resources:library:test |
||||
./gradlew :resources:library:desktopTest |
||||
|
||||
# Android integration tests |
||||
./gradlew :resources:library:pixel5DebugAndroidTest |
||||
|
@ -0,0 +1,13 @@
|
||||
#!/bin/bash |
||||
|
||||
cd "$(dirname "$0")" |
||||
|
||||
if [[ -z "$COMPOSE_CUSTOM_VERSION" ]]; then |
||||
echo "Must provide COMPOSE_CUSTOM_VERSION in environment" 1>&2 |
||||
exit 1 |
||||
fi |
||||
|
||||
pushd ../../components |
||||
./gradlew publishToMavenLocal -Pcompose.version="$COMPOSE_CUSTOM_VERSION" || exit 1 |
||||
popd |
||||
|
Loading…
Reference in new issue