Skip to content
New issue

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

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

Already on GitHub? Sign in to your account

Add WatchOS support #648

Merged
merged 18 commits into from
Apr 19, 2024
Merged
Show file tree
Hide file tree
Changes from 11 commits
Commits
Show all changes
18 commits
Select commit Hold shift + click to select a range
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -31,4 +31,10 @@ kotlin {
dependsOn(appleTest)
}
}

sourceSets.matching {
it.name == "watchosMain"
}.configureEach {
this.dependsOn(sourceSets.getByName("appleMain"))
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -26,6 +26,8 @@ kotlin {
}

sourceSets {
val commonMain by getting

val iosX64Main by getting
val iosArm64Main by getting
val iosSimulatorArm64Main by getting
Expand All @@ -46,7 +48,6 @@ kotlin {
iosSimulatorArm64Test.dependsOn(this)
}

val commonMain by getting
val macosArm64Main by getting
val macosX64Main by getting

Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,35 @@
/*
* Copyright 2024 IceRock MAG Inc. Use of this source code is governed by the Apache 2.0 license.
*/

plugins {
id("multiplatform-library-convention")
}

kotlin {
watchosX64()
watchosArm32()
watchosArm64()
watchosSimulatorArm64()

sourceSets {
val commonMain by getting

val watchosMain by creating {
dependsOn(commonMain)
}
val watchosX64Main by getting {
dependsOn(watchosMain)
}
val watchosArm32Main by getting{
dependsOn(watchosMain)
}
val watchosArm64Main by getting{
dependsOn(watchosMain)
}
val watchosSimulatorArm64Main by getting{
dependsOn(watchosMain)
}
}
}

Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,6 @@ import kotlinx.cinterop.CPointed
import kotlinx.cinterop.CPointer
import kotlinx.cinterop.CValue
import kotlinx.cinterop.ExperimentalForeignApi
import kotlinx.cinterop.readBytes
import kotlinx.cinterop.refTo
import org.jetbrains.skia.ColorAlphaType
import org.jetbrains.skia.ColorType
Expand Down
2 changes: 1 addition & 1 deletion resources-test/build.gradle.kts
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@
*/

plugins {
id("multiplatform-library-convention")
id("multiplatform-library-extended-convention")
id("multiplatform-android-publish-convention")
id("apple-main-convention")
id("detekt-convention")
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
/*
* Copyright 2021 IceRock MAG Inc. Use of this source code is governed by the Apache 2.0 license.
*/

package dev.icerock.moko.resources.test

import dev.icerock.moko.resources.FontResource
import dev.icerock.moko.resources.ImageResource

actual fun createImageResourceMock(): ImageResource = ImageResource("")
actual fun createFontResourceMock(): FontResource = FontResource("")
2 changes: 1 addition & 1 deletion resources/build.gradle.kts
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@
*/

plugins {
id("multiplatform-library-convention")
id("multiplatform-library-extended-convention")
id("multiplatform-android-publish-convention")
id("apple-main-convention")
id("kotlin-parcelize")
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -4,10 +4,17 @@

package dev.icerock.moko.resources

import cnames.structs.CGDataProvider
import cnames.structs.CGFont
import cnames.structs.__CFData
import cnames.structs.__CFString
import cnames.structs.__CFURL
import kotlinx.cinterop.BetaInteropApi
import kotlinx.cinterop.CPointer
import kotlinx.cinterop.ExperimentalForeignApi
import kotlinx.cinterop.UnsafeNumber
import kotlinx.cinterop.alloc
import kotlinx.cinterop.convert
import kotlinx.cinterop.memScoped
import kotlinx.cinterop.ptr
import kotlinx.cinterop.value
Expand Down Expand Up @@ -35,18 +42,18 @@ import platform.darwin.UInt8Var

actual class FontResource(
val fontName: String,
val bundle: NSBundle = NSBundle.mainBundle
val bundle: NSBundle = NSBundle.mainBundle,
) {
@OptIn(ExperimentalForeignApi::class)
@OptIn(ExperimentalForeignApi::class, UnsafeNumber::class)
internal val fontRef: CGFontRef by lazy {
val fontData: NSData = this.data
val cfDataRef: CPointer<__CFData>? = CFDataCreate(
kCFAllocatorDefault,
fontData.bytes() as CPointer<UInt8Var>,
fontData.length.toLong()
allocator = kCFAllocatorDefault,
bytes = fontData.bytes() as CPointer<UInt8Var>,
length = fontData.length.toLong().convert()
)
val dataProvider = CGDataProviderCreateWithCFData(cfDataRef)
val cgFont = CGFontCreateWithDataProvider(dataProvider)!!
val dataProvider: CPointer<CGDataProvider>? = CGDataProviderCreateWithCFData(cfDataRef)
val cgFont: CPointer<CGFont> = CGFontCreateWithDataProvider(dataProvider)!!

CGDataProviderRelease(dataProvider)
CFRelease(cfDataRef)
Expand All @@ -62,29 +69,41 @@ actual class FontResource(
) ?: error("file $fontName not found in $bundle")
}

@OptIn(BetaInteropApi::class)
val data: NSData
get() {
val filePath: String = this.filePath
return NSData.create(contentsOfFile = filePath)
?: error("can't read $filePath file")
}

@OptIn(ExperimentalForeignApi::class)
@OptIn(ExperimentalForeignApi::class, UnsafeNumber::class)
@Throws(NSErrorException::class)
@Suppress("unused")
fun registerFont() {
// CAST_NEVER_SUCCEEDS - String is final and isn't castable, but on iOS it's
// an NSString so `as NSString` is fine.
// UNCHECKED_CAST - NSString and CFStringRef are toll-free bridged
@Suppress("CAST_NEVER_SUCCEEDS", "UNCHECKED_CAST")
val cfStringFilePath = CFBridgingRetain(filePath as NSString) as CFStringRef
val cfFontUrlRef = CFURLCreateWithFileSystemPath(kCFAllocatorDefault, cfStringFilePath, kCFURLPOSIXPathStyle, false)
val cfStringFilePath: CPointer<__CFString> =
CFBridgingRetain(filePath as NSString) as CFStringRef
val cfFontUrlRef: CPointer<__CFURL>? = CFURLCreateWithFileSystemPath(
allocator = kCFAllocatorDefault,
filePath = cfStringFilePath,
pathStyle = kCFURLPOSIXPathStyle,
isDirectory = false
)

var nsError: NSError? = null

memScoped {
val error = alloc<CFErrorRefVar>()
if (!CTFontManagerRegisterFontsForURL(cfFontUrlRef, kCTFontManagerScopeProcess, error.ptr)) {
if (!CTFontManagerRegisterFontsForURL(
fontURL = cfFontUrlRef,
scope = kCTFontManagerScopeProcess,
error = error.ptr
)
) {
error.value?.let {
nsError = CFBridgingRelease(it) as NSError
}
Expand Down
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
/*
* Copyright 2023 IceRock MAG Inc. Use of this source code is governed by the Apache 2.0 license.
* Copyright 2024 IceRock MAG Inc. Use of this source code is governed by the Apache 2.0 license.
*/

package dev.icerock.moko.resources
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
/*
* Copyright 2020 IceRock MAG Inc. Use of this source code is governed by the Apache 2.0 license.
*/

package dev.icerock.moko.resources

import platform.UIKit.UIColor
import platform.UIKit.colorNamed

fun ColorResource.getUIColor(): UIColor {
val color: UIColor? = UIColor.colorNamed(name = this.name)
return requireNotNull(color) {
"Can't read color $name, please check moko-resources gradle configuration"
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,26 @@
/*
* Copyright 2024 IceRock MAG Inc. Use of this source code is governed by the Apache 2.0 license.
*/

package dev.icerock.moko.resources

import cnames.structs.__CTFont
import kotlinx.cinterop.CPointer
import kotlinx.cinterop.ExperimentalForeignApi
import kotlinx.cinterop.UnsafeNumber
import platform.CoreGraphics.CGFloat
import platform.CoreText.CTFontCreateWithGraphicsFont
import platform.Foundation.CFBridgingRelease
import platform.UIKit.UIFont

@OptIn(ExperimentalForeignApi::class, UnsafeNumber::class)
fun FontResource.uiFont(withSize: Double): UIFont {
val ctFont: CPointer<__CTFont>? = CTFontCreateWithGraphicsFont(
graphicsFont = fontRef,
size = withSize.toFloat() as CGFloat,
matrix = null,
attributes = null
)

return CFBridgingRelease(ctFont) as UIFont
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
/*
* Copyright 2019 IceRock MAG Inc. Use of this source code is governed by the Apache 2.0 license.
*/

package dev.icerock.moko.resources

import platform.UIKit.UIImage

actual class ImageResource(val assetImageName: String) {
fun toUIImage(): UIImage? {
return UIImage.imageNamed(name = assetImageName)
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
/*
* Copyright 2020 IceRock MAG Inc. Use of this source code is governed by the Apache 2.0 license.
*/

package dev.icerock.moko.resources

actual fun ResourceContainer<ImageResource>.getImageByFileName(fileName: String): ImageResource? {
return ImageResource(fileName).let { imgRes ->
if (imgRes.toUIImage() != null) imgRes else null
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,33 @@
/*
* Copyright 2023 IceRock MAG Inc. Use of this source code is governed by the Apache 2.0 license.
*/

package dev.icerock.moko.resources.desc.color

import dev.icerock.moko.graphics.Color
import dev.icerock.moko.resources.getUIColor
import kotlinx.cinterop.UnsafeNumber
import platform.CoreGraphics.CGFloat
import platform.UIKit.UIColor

fun ColorDesc.getUIColor(): UIColor {
return when (this) {
is ColorDescResource -> resource.getUIColor()
is ColorDescSingle -> color.toUIColor()
// light / dark mode seems unsupported on watchos, fallback to lightColor
is ColorDescThemed -> lightColor.toUIColor()
else -> throw IllegalArgumentException("unknown class ${this::class}")
}
}

// TODO: Replace after update moko-graphics with support of WatchOS
@OptIn(UnsafeNumber::class)
private fun Color.toUIColor(): UIColor {
val maxColorValue = 0xFF
return UIColor(
red = red.toFloat() as CGFloat / maxColorValue,
green = green.toFloat() as CGFloat / maxColorValue,
blue = blue.toFloat() as CGFloat / maxColorValue,
alpha = alpha.toFloat() as CGFloat / maxColorValue
)
}
2 changes: 1 addition & 1 deletion samples/kotlin-ios-app/build.gradle.kts
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,6 @@ buildscript {
}
dependencies {
classpath(moko.resourcesGradlePlugin)
classpath("org.jetbrains.kotlin:kotlin-gradle-plugin:1.9.10")
classpath("org.jetbrains.kotlin:kotlin-gradle-plugin:1.9.20")
}
}
32 changes: 30 additions & 2 deletions samples/kotlin-ios-app/mpp-library/build.gradle.kts
Original file line number Diff line number Diff line change
Expand Up @@ -10,16 +10,44 @@ plugins {
kotlin {
ios()
iosSimulatorArm64()

val watchosX64 = watchosX64()
val watchosArm64 = watchosArm64()
val watchosSimulatorArm64 = watchosSimulatorArm64()
configure(listOf(watchosX64, watchosArm64, watchosSimulatorArm64)) {
binaries {
framework {
baseName = "MppLibrary"
}
}
}


sourceSets {
val iosMain by getting {
val commonMain by getting

val appleMain by creating {
dependsOn(commonMain)
dependencies {
api(moko.resources)
}
}

val iosMain by getting {
dependsOn(appleMain)
}
val iosSimulatorArm64Main by getting {
dependsOn(iosMain)
}

val watchosMain by creating {
dependsOn(appleMain)
}
val watchosX64Main by getting
val watchosArm64Main by getting
val watchosSimulatorArm64Main by getting
watchosX64Main.dependsOn(watchosMain)
watchosArm64Main.dependsOn(watchosMain)
watchosSimulatorArm64Main.dependsOn(watchosMain)
}
}

Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
{
"colors" : [
{
"idiom" : "universal"
}
],
"info" : {
"author" : "xcode",
"version" : 1
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
{
"images" : [
{
"idiom" : "universal",
"platform" : "watchos",
"size" : "1024x1024"
}
],
"info" : {
"author" : "xcode",
"version" : 1
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
{
"info" : {
"author" : "xcode",
"version" : 1
}
}
Loading