Add support for macOS and backport to older OS versions
This commit is contained in:
@@ -8,3 +8,7 @@ org.gradle.caching=true
|
||||
#Android
|
||||
android.nonTransitiveRClass=true
|
||||
android.useAndroidX=true
|
||||
#Compose Multiplatform
|
||||
org.jetbrains.compose.experimental.macos.enabled=true
|
||||
#Kotlin/Native
|
||||
kotlin.native.ignoreDisabledTargets=true
|
||||
Binary file not shown.
|
After Width: | Height: | Size: 6.4 KiB |
@@ -11,7 +11,7 @@
|
||||
/* End PBXFileReference section */
|
||||
|
||||
/* Begin PBXFileSystemSynchronizedBuildFileExceptionSet section */
|
||||
D262C3356B38E7E13FFE64E4 /* PBXFileSystemSynchronizedBuildFileExceptionSet */ = {
|
||||
D262C3356B38E7E13FFE64E4 /* Exceptions for "iosApp" folder in "iosApp" target */ = {
|
||||
isa = PBXFileSystemSynchronizedBuildFileExceptionSet;
|
||||
membershipExceptions = (
|
||||
Info.plist,
|
||||
@@ -23,21 +23,13 @@
|
||||
/* Begin PBXFileSystemSynchronizedRootGroup section */
|
||||
24D66E9CCFCEB2D844328B79 /* Configuration */ = {
|
||||
isa = PBXFileSystemSynchronizedRootGroup;
|
||||
explicitFileTypes = {
|
||||
};
|
||||
explicitFolders = (
|
||||
);
|
||||
path = Configuration;
|
||||
sourceTree = "<group>";
|
||||
};
|
||||
55D8B06C58654F251C5B3381 /* iosApp */ = {
|
||||
isa = PBXFileSystemSynchronizedRootGroup;
|
||||
exceptions = (
|
||||
D262C3356B38E7E13FFE64E4 /* PBXFileSystemSynchronizedBuildFileExceptionSet */,
|
||||
);
|
||||
explicitFileTypes = {
|
||||
};
|
||||
explicitFolders = (
|
||||
D262C3356B38E7E13FFE64E4 /* Exceptions for "iosApp" folder in "iosApp" target */,
|
||||
);
|
||||
path = iosApp;
|
||||
sourceTree = "<group>";
|
||||
@@ -84,6 +76,7 @@
|
||||
5BCD356C334571BCAA0967CA /* Sources */,
|
||||
C2B5A146484C5EC128AEFD65 /* Frameworks */,
|
||||
96EB3DBC5DECE4CD4D246992 /* Resources */,
|
||||
1DADA2DD199949A498015FFD /* Sanitize Moko Resource Bundle Names */,
|
||||
);
|
||||
buildRules = (
|
||||
);
|
||||
@@ -181,6 +174,24 @@
|
||||
shellPath = /bin/sh;
|
||||
shellScript = "\"$SRCROOT/../gradlew\" -p \"$SRCROOT/../\" :shared:copyFrameworkResourcesToApp \\\n -Pmoko.resources.PLATFORM_NAME=\"$PLATFORM_NAME\" \\\n -Pmoko.resources.CONFIGURATION=\"$CONFIGURATION\" \\\n -Pmoko.resources.ARCHS=\"$ARCHS\" \\\n -Pmoko.resources.BUILT_PRODUCTS_DIR=\"$BUILT_PRODUCTS_DIR\" \\\n -Pmoko.resources.CONTENTS_FOLDER_PATH=\"$CONTENTS_FOLDER_PATH\" \n";
|
||||
};
|
||||
1DADA2DD199949A498015FFD /* Sanitize Moko Resource Bundle Names */ = {
|
||||
isa = PBXShellScriptBuildPhase;
|
||||
buildActionMask = 2147483647;
|
||||
files = (
|
||||
);
|
||||
inputFileListPaths = (
|
||||
);
|
||||
inputPaths = (
|
||||
);
|
||||
name = "Sanitize Moko Resource Bundle Names";
|
||||
outputFileListPaths = (
|
||||
);
|
||||
outputPaths = (
|
||||
);
|
||||
runOnlyForDeploymentPostprocessing = 0;
|
||||
shellPath = /bin/sh;
|
||||
shellScript = "if [ \"$PLATFORM_NAME\" != \"macosx\" ]; then\n exit 0\nfi\n# moko-resources names its resource bundle after the Kotlin klib unique_name (group:module),\n# e.g. SatsPrice:shared.bundle, and drops it directly under Contents/. Colons break\n# codesign, and a loose bundle outside Contents/Resources isn't recognized as a proper\n# nested bundle either, so the final app-level CodeSign phase fails with \"code object is\n# not signed at all\" unless we rename and relocate it. NSBundle resolves this bundle by\n# directory identity, not by name/path, so moko-resources/localization lookups still work.\nfind \"$CODESIGNING_FOLDER_PATH\" -type d -name \"*:*.bundle\" | while IFS= read -r bundle; do\n safe_name=$(basename \"$bundle\" | tr ':' '_')\n mkdir -p \"$CODESIGNING_FOLDER_PATH/Contents/Resources\"\n dest=\"$CODESIGNING_FOLDER_PATH/Contents/Resources/$safe_name\"\n rm -rf \"$dest\"\n mv \"$bundle\" \"$dest\"\ndone\n";
|
||||
};
|
||||
/* End PBXShellScriptBuildPhase section */
|
||||
|
||||
/* Begin PBXSourcesBuildPhase section */
|
||||
@@ -200,18 +211,21 @@
|
||||
ARCHS = arm64;
|
||||
ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon;
|
||||
ASSETCATALOG_COMPILER_GLOBAL_ACCENT_COLOR_NAME = AccentColor;
|
||||
"CODE_SIGN_ENTITLEMENTS[sdk=macosx*]" = iosApp/macOS.entitlements;
|
||||
CODE_SIGN_IDENTITY = "Apple Development";
|
||||
"CODE_SIGN_IDENTITY[sdk=macosx*]" = "Apple Development";
|
||||
CODE_SIGN_STYLE = Automatic;
|
||||
DEVELOPMENT_ASSET_PATHS = "\"iosApp/Preview Content\"";
|
||||
DEVELOPMENT_TEAM = "${TEAM_ID}";
|
||||
DEVELOPMENT_TEAM = S99A5B637C;
|
||||
ENABLE_PREVIEWS = YES;
|
||||
GENERATE_INFOPLIST_FILE = YES;
|
||||
INFOPLIST_FILE = iosApp/Info.plist;
|
||||
INFOPLIST_KEY_UIApplicationSceneManifest_Generation = YES;
|
||||
INFOPLIST_KEY_UIApplicationSupportsIndirectInputEvents = YES;
|
||||
INFOPLIST_KEY_UILaunchScreen_Generation = YES;
|
||||
INFOPLIST_KEY_UISupportedInterfaceOrientations_iPad = "UIInterfaceOrientationPortrait UIInterfaceOrientationPortraitUpsideDown UIInterfaceOrientationLandscapeLeft UIInterfaceOrientationLandscapeRight";
|
||||
INFOPLIST_KEY_UISupportedInterfaceOrientations_iPhone = "UIInterfaceOrientationPortrait UIInterfaceOrientationLandscapeLeft UIInterfaceOrientationLandscapeRight";
|
||||
INFOPLIST_KEY_LSApplicationCategoryType = "public.app-category.utilities";
|
||||
"INFOPLIST_KEY_UIApplicationSceneManifest_Generation[sdk=iphone*]" = YES;
|
||||
"INFOPLIST_KEY_UIApplicationSupportsIndirectInputEvents[sdk=iphone*]" = YES;
|
||||
"INFOPLIST_KEY_UILaunchScreen_Generation[sdk=iphone*]" = YES;
|
||||
"INFOPLIST_KEY_UISupportedInterfaceOrientations_iPad[sdk=iphone*]" = "UIInterfaceOrientationPortrait UIInterfaceOrientationPortraitUpsideDown UIInterfaceOrientationLandscapeLeft UIInterfaceOrientationLandscapeRight";
|
||||
"INFOPLIST_KEY_UISupportedInterfaceOrientations_iPhone[sdk=iphone*]" = "UIInterfaceOrientationPortrait UIInterfaceOrientationLandscapeLeft UIInterfaceOrientationLandscapeRight";
|
||||
LD_RUNPATH_SEARCH_PATHS = (
|
||||
"$(inherited)",
|
||||
"@executable_path/Frameworks",
|
||||
@@ -270,11 +284,13 @@
|
||||
GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE;
|
||||
GCC_WARN_UNUSED_FUNCTION = YES;
|
||||
GCC_WARN_UNUSED_VARIABLE = YES;
|
||||
IPHONEOS_DEPLOYMENT_TARGET = 18.2;
|
||||
IPHONEOS_DEPLOYMENT_TARGET = 16.0;
|
||||
LOCALIZATION_PREFERS_STRING_CATALOGS = YES;
|
||||
MACOSX_DEPLOYMENT_TARGET = 13.0;
|
||||
MTL_ENABLE_DEBUG_INFO = NO;
|
||||
MTL_FAST_MATH = YES;
|
||||
SDKROOT = iphoneos;
|
||||
SDKROOT = auto;
|
||||
SUPPORTED_PLATFORMS = "iphoneos iphonesimulator macosx";
|
||||
SWIFT_COMPILATION_MODE = wholemodule;
|
||||
VALIDATE_PRODUCT = YES;
|
||||
};
|
||||
@@ -334,12 +350,14 @@
|
||||
GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE;
|
||||
GCC_WARN_UNUSED_FUNCTION = YES;
|
||||
GCC_WARN_UNUSED_VARIABLE = YES;
|
||||
IPHONEOS_DEPLOYMENT_TARGET = 18.2;
|
||||
IPHONEOS_DEPLOYMENT_TARGET = 16.0;
|
||||
LOCALIZATION_PREFERS_STRING_CATALOGS = YES;
|
||||
MACOSX_DEPLOYMENT_TARGET = 13.0;
|
||||
MTL_ENABLE_DEBUG_INFO = INCLUDE_SOURCE;
|
||||
MTL_FAST_MATH = YES;
|
||||
ONLY_ACTIVE_ARCH = YES;
|
||||
SDKROOT = iphoneos;
|
||||
SDKROOT = auto;
|
||||
SUPPORTED_PLATFORMS = "iphoneos iphonesimulator macosx";
|
||||
SWIFT_ACTIVE_COMPILATION_CONDITIONS = "DEBUG $(inherited)";
|
||||
SWIFT_OPTIMIZATION_LEVEL = "-Onone";
|
||||
};
|
||||
@@ -351,18 +369,21 @@
|
||||
ARCHS = arm64;
|
||||
ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon;
|
||||
ASSETCATALOG_COMPILER_GLOBAL_ACCENT_COLOR_NAME = AccentColor;
|
||||
"CODE_SIGN_ENTITLEMENTS[sdk=macosx*]" = iosApp/macOS.entitlements;
|
||||
CODE_SIGN_IDENTITY = "Apple Development";
|
||||
"CODE_SIGN_IDENTITY[sdk=macosx*]" = "Apple Development";
|
||||
CODE_SIGN_STYLE = Automatic;
|
||||
DEVELOPMENT_ASSET_PATHS = "\"iosApp/Preview Content\"";
|
||||
DEVELOPMENT_TEAM = "${TEAM_ID}";
|
||||
DEVELOPMENT_TEAM = S99A5B637C;
|
||||
ENABLE_PREVIEWS = YES;
|
||||
GENERATE_INFOPLIST_FILE = YES;
|
||||
INFOPLIST_FILE = iosApp/Info.plist;
|
||||
INFOPLIST_KEY_UIApplicationSceneManifest_Generation = YES;
|
||||
INFOPLIST_KEY_UIApplicationSupportsIndirectInputEvents = YES;
|
||||
INFOPLIST_KEY_UILaunchScreen_Generation = YES;
|
||||
INFOPLIST_KEY_UISupportedInterfaceOrientations_iPad = "UIInterfaceOrientationPortrait UIInterfaceOrientationPortraitUpsideDown UIInterfaceOrientationLandscapeLeft UIInterfaceOrientationLandscapeRight";
|
||||
INFOPLIST_KEY_UISupportedInterfaceOrientations_iPhone = "UIInterfaceOrientationPortrait UIInterfaceOrientationLandscapeLeft UIInterfaceOrientationLandscapeRight";
|
||||
INFOPLIST_KEY_LSApplicationCategoryType = "public.app-category.utilities";
|
||||
"INFOPLIST_KEY_UIApplicationSceneManifest_Generation[sdk=iphone*]" = YES;
|
||||
"INFOPLIST_KEY_UIApplicationSupportsIndirectInputEvents[sdk=iphone*]" = YES;
|
||||
"INFOPLIST_KEY_UILaunchScreen_Generation[sdk=iphone*]" = YES;
|
||||
"INFOPLIST_KEY_UISupportedInterfaceOrientations_iPad[sdk=iphone*]" = "UIInterfaceOrientationPortrait UIInterfaceOrientationPortraitUpsideDown UIInterfaceOrientationLandscapeLeft UIInterfaceOrientationLandscapeRight";
|
||||
"INFOPLIST_KEY_UISupportedInterfaceOrientations_iPhone[sdk=iphone*]" = "UIInterfaceOrientationPortrait UIInterfaceOrientationLandscapeLeft UIInterfaceOrientationLandscapeRight";
|
||||
LD_RUNPATH_SEARCH_PATHS = (
|
||||
"$(inherited)",
|
||||
"@executable_path/Frameworks",
|
||||
|
||||
@@ -6,7 +6,7 @@ struct ContentView: View {
|
||||
@State private var showCurrencyPicker = false
|
||||
|
||||
var body: some View {
|
||||
NavigationView {
|
||||
NavigationStack {
|
||||
Group {
|
||||
if let state = viewModel.state {
|
||||
form(for: state)
|
||||
@@ -121,13 +121,18 @@ struct ContentView: View {
|
||||
onChange: { viewModel.onFiatAmountChanged(code: row.code, value: $0) }
|
||||
)
|
||||
}
|
||||
#if os(iOS)
|
||||
.onDelete { indexSet in
|
||||
for index in indexSet {
|
||||
viewModel.onFiatCurrencyToggled(state.fiatRows[index].code)
|
||||
}
|
||||
}
|
||||
#endif
|
||||
}
|
||||
}
|
||||
#if os(macOS)
|
||||
.formStyle(.grouped)
|
||||
#endif
|
||||
.sheet(isPresented: $showCurrencyPicker) {
|
||||
CurrencyPickerSheet(
|
||||
currencies: state.availableCurrencies,
|
||||
@@ -142,7 +147,7 @@ struct ContentView: View {
|
||||
private func amountRow(
|
||||
label: String,
|
||||
value: String,
|
||||
keyboardType: UIKeyboardType,
|
||||
keyboardType: NumericFieldKeyboard,
|
||||
sanitize: @escaping (String) -> String,
|
||||
onChange: @escaping (String) -> Void
|
||||
) -> some View {
|
||||
@@ -150,7 +155,7 @@ struct ContentView: View {
|
||||
Text(label)
|
||||
Spacer()
|
||||
NumericField(
|
||||
placeholder: label,
|
||||
placeholder: "",
|
||||
value: value,
|
||||
keyboardType: keyboardType,
|
||||
sanitize: sanitize,
|
||||
@@ -170,7 +175,7 @@ struct ContentView: View {
|
||||
private struct NumericField: View {
|
||||
let placeholder: String
|
||||
let value: String
|
||||
let keyboardType: UIKeyboardType
|
||||
let keyboardType: NumericFieldKeyboard
|
||||
let sanitize: (String) -> String
|
||||
let onChange: (String) -> Void
|
||||
var alignment: TextAlignment = .leading
|
||||
@@ -179,10 +184,15 @@ private struct NumericField: View {
|
||||
|
||||
var body: some View {
|
||||
TextField(placeholder, text: $text)
|
||||
.keyboardType(keyboardType)
|
||||
#if os(iOS)
|
||||
.keyboardType(keyboardType.uiKeyboardType)
|
||||
#endif
|
||||
.multilineTextAlignment(alignment)
|
||||
#if os(macOS)
|
||||
.frame(maxWidth: 140)
|
||||
#endif
|
||||
.onAppear { text = value }
|
||||
.onChange(of: text) { _, newValue in
|
||||
.onChange(of: text) { newValue in
|
||||
let sanitized = sanitize(newValue)
|
||||
if sanitized != newValue {
|
||||
text = sanitized
|
||||
@@ -191,7 +201,7 @@ private struct NumericField: View {
|
||||
onChange(sanitized)
|
||||
}
|
||||
}
|
||||
.onChange(of: value) { _, newValue in
|
||||
.onChange(of: value) { newValue in
|
||||
if newValue != text {
|
||||
text = newValue
|
||||
}
|
||||
@@ -216,3 +226,21 @@ private func sanitizeDecimalInput(_ raw: String) -> String {
|
||||
private func sanitizeIntegerInput(_ raw: String) -> String {
|
||||
raw.filter { $0.isNumber }
|
||||
}
|
||||
|
||||
/// `UIKeyboardType` doesn't exist on macOS (no on-screen keyboard), so this stands in for it
|
||||
/// across both platforms; only iOS actually applies it, via `uiKeyboardType` below.
|
||||
private enum NumericFieldKeyboard {
|
||||
case decimalPad
|
||||
case numberPad
|
||||
}
|
||||
|
||||
#if os(iOS)
|
||||
private extension NumericFieldKeyboard {
|
||||
var uiKeyboardType: UIKeyboardType {
|
||||
switch self {
|
||||
case .decimalPad: return .decimalPad
|
||||
case .numberPad: return .numberPad
|
||||
}
|
||||
}
|
||||
}
|
||||
#endif
|
||||
|
||||
@@ -10,7 +10,7 @@ struct CurrencyPickerSheet: View {
|
||||
@Environment(\.dismiss) private var dismiss
|
||||
|
||||
var body: some View {
|
||||
NavigationView {
|
||||
NavigationStack {
|
||||
List(currencies, id: \.code) { info in
|
||||
Button {
|
||||
onToggle(info.code)
|
||||
@@ -27,13 +27,21 @@ struct CurrencyPickerSheet: View {
|
||||
}
|
||||
}
|
||||
.navigationTitle(IosLocalizationKt.localizedString(resource: MR.strings.shared.currencies_section_title))
|
||||
#if os(iOS)
|
||||
.navigationBarTitleDisplayMode(.inline)
|
||||
#endif
|
||||
.toolbar {
|
||||
ToolbarItem(placement: .confirmationAction) {
|
||||
Button(IosLocalizationKt.localizedString(resource: MR.strings.shared.done)) { dismiss() }
|
||||
}
|
||||
}
|
||||
}
|
||||
#if os(macOS)
|
||||
// macOS sizes a .sheet() to its content's ideal size rather than the parent window's
|
||||
// size (unlike iOS, which presents modally full-size); without an explicit frame here,
|
||||
// the List has no size to lay out rows in and renders empty.
|
||||
.frame(minWidth: 420, minHeight: 480)
|
||||
#endif
|
||||
}
|
||||
|
||||
private func currencyLabel(for info: CurrencyInfo) -> String {
|
||||
|
||||
@@ -5,6 +5,9 @@ struct iOSApp: App {
|
||||
var body: some Scene {
|
||||
WindowGroup {
|
||||
ContentView()
|
||||
#if os(macOS)
|
||||
.frame(minWidth: 420, minHeight: 500)
|
||||
#endif
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -0,0 +1,10 @@
|
||||
<?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>com.apple.security.app-sandbox</key>
|
||||
<true/>
|
||||
<key>com.apple.security.network.client</key>
|
||||
<true/>
|
||||
</dict>
|
||||
</plist>
|
||||
@@ -13,9 +13,10 @@ plugins {
|
||||
kotlin {
|
||||
listOf(
|
||||
iosArm64(),
|
||||
iosSimulatorArm64()
|
||||
).forEach { iosTarget ->
|
||||
iosTarget.binaries.framework {
|
||||
iosSimulatorArm64(),
|
||||
macosArm64()
|
||||
).forEach { appleTarget ->
|
||||
appleTarget.binaries.framework {
|
||||
baseName = "Shared"
|
||||
isStatic = true
|
||||
export(libs.moko.resources)
|
||||
@@ -87,7 +88,7 @@ kotlin {
|
||||
implementation(libs.kotlinx.coroutinesTest)
|
||||
implementation(libs.ktor.client.mock)
|
||||
}
|
||||
iosMain.dependencies {
|
||||
appleMain.dependencies {
|
||||
implementation(libs.ktor.client.darwin)
|
||||
}
|
||||
jvmMain.dependencies {
|
||||
|
||||
@@ -0,0 +1,9 @@
|
||||
package xyz.tyiu.satsprice
|
||||
|
||||
import platform.Foundation.NSProcessInfo
|
||||
|
||||
class MacOSPlatform : Platform {
|
||||
override val name: String = "macOS " + NSProcessInfo.processInfo.operatingSystemVersionString
|
||||
}
|
||||
|
||||
actual fun getPlatform(): Platform = MacOSPlatform()
|
||||
Reference in New Issue
Block a user