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 9 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 @@ -16,6 +16,15 @@ kotlin {
val iosMain by getting {
dependsOn(appleMain)
}
val watchosMain by getting {
dependsOn(appleMain)
}
val watchos64Main by getting {
dependsOn(watchosMain)
}
val watchos32Main by getting {
dependsOn(watchosMain)
}
val macosMain by getting {
dependsOn(appleMain)
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -46,6 +46,18 @@ kotlin {
iosSimulatorArm64Test.dependsOn(this)
}

val watchosMain by creating
val watchos64Main by creating
val watchos32Main by creating
val watchosX64Main by creating
val watchosArm64Main by creating
val watchosSimulatorArm64Main by creating
watchos64Main.dependsOn(watchosMain)
watchos32Main.dependsOn(watchosMain)
watchosX64Main.dependsOn(watchos64Main)
watchosArm64Main.dependsOn(watchos32Main)
watchosSimulatorArm64Main.dependsOn(watchos64Main)

val commonMain by getting
val macosArm64Main by getting
val macosX64Main by getting
Expand Down
6 changes: 6 additions & 0 deletions resources-test/build.gradle.kts
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,12 @@ plugins {
id("publication-convention")
}

kotlin {
watchosX64()
watchosArm64()
watchosSimulatorArm64()
}

android {
namespace = "dev.icerock.moko.resources.test"
}
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("")
4 changes: 4 additions & 0 deletions resources/build.gradle.kts
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,10 @@ plugins {
}

kotlin {
watchosX64()
watchosArm64()
watchosSimulatorArm64()

sourceSets {
getByName("jsMain") {
dependencies {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -5,9 +5,12 @@
package dev.icerock.moko.resources

import cnames.structs.__CFData
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,15 +38,15 @@ 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()
fontData.length.toLong().convert()
)
val dataProvider = CGDataProviderCreateWithCFData(cfDataRef)
val cgFont = CGFontCreateWithDataProvider(dataProvider)!!
Expand All @@ -62,14 +65,15 @@ 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() {
Expand All @@ -78,13 +82,23 @@ actual class FontResource(
// 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 cfFontUrlRef = CFURLCreateWithFileSystemPath(
kCFAllocatorDefault,
cfStringFilePath,
kCFURLPOSIXPathStyle,
false
)

var nsError: NSError? = null

memScoped {
val error = alloc<CFErrorRefVar>()
if (!CTFontManagerRegisterFontsForURL(cfFontUrlRef, kCTFontManagerScopeProcess, error.ptr)) {
if (!CTFontManagerRegisterFontsForURL(
cfFontUrlRef,
kCTFontManagerScopeProcess,
error.ptr
)
) {
error.value?.let {
nsError = CFBridgingRelease(it) as NSError
}
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/gradle.properties
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
org.gradle.jvmargs=-Xmx12g
org.gradle.parallel=true
org.gradle.caching=true
org.gradle.configuration-cache=true
org.gradle.configuration-cache=false

kotlin.code.style=official

Expand Down
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
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,26 @@
//
// ContentView.swift
// TestKotlinWatchApp
//
// Created by Cornelli Fabio on 28/02/24.
// Copyright © 2024 IceRock Development. All rights reserved.
//

import SwiftUI
import MppLibrary

struct ContentView: View {
var body: some View {
VStack {
Image(systemName: "globe")
.imageScale(.large)
.foregroundStyle(.tint)
Text(Testing.shared.getHelloWorld().localized())
}
.padding()
}
}

#Preview {
ContentView()
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
//
// TestKotlinWatchApp.swift
// TestKotlinWatchApp
//
// Created by Cornelli Fabio on 28/02/24.
// Copyright © 2024 IceRock Development. All rights reserved.
//

import SwiftUI

@main
struct TestKotlinWatchApp: App {
var body: some Scene {
WindowGroup {
ContentView()
}
}
}
Loading