Google Play 단독 출시로 정리 + 스토어 제출 자산 준비
iOS 타깃을 제거했다. Apple Developer Program 연 $99 와 맥이 필요한데 아이폰은 기존 웹 PWA 로 이미 커버되므로 비용 대비 실익이 없다고 판단했다. commonMain 중심 구조는 그대로라 iosTarget 만 다시 넣으면 복구된다. 멀티·리그전/커스텀 카드에 '준비 중' 배지를 붙였다. 구현되지 않은 모드가 스토어 스크린샷에서 동작하는 것처럼 보이면 Play 의 허위 표시 정책에 걸린다. store/ 에 Play Console 제출 자산을 모았다: 등록정보 문구, 데이터 보안 설문 답안(릴리스 APK 에 INTERNET 권한이 없음을 aapt2 로 확인), 개인정보처리방침, 피처 그래픽 1024x500, 스토어 아이콘 512x512, 폰 4장 + 태블릿 3장 스크린샷.
This commit is contained in:
@@ -1,4 +1,3 @@
|
||||
import org.jetbrains.compose.desktop.application.dsl.TargetFormat
|
||||
import org.jetbrains.kotlin.gradle.ExperimentalKotlinGradlePluginApi
|
||||
import org.jetbrains.kotlin.gradle.dsl.JvmTarget
|
||||
import java.util.Properties
|
||||
@@ -23,14 +22,9 @@ kotlin {
|
||||
compilerOptions { jvmTarget.set(JvmTarget.JVM_17) }
|
||||
}
|
||||
|
||||
// iOS 타깃 — 실제 컴파일은 macOS + Xcode 에서만 가능하다.
|
||||
// 리눅스에서는 설정만 되고 빌드 태스크를 실행하지 않는다.
|
||||
listOf(iosX64(), iosArm64(), iosSimulatorArm64()).forEach { iosTarget ->
|
||||
iosTarget.binaries.framework {
|
||||
baseName = "ComposeApp"
|
||||
isStatic = true
|
||||
}
|
||||
}
|
||||
// iOS 타깃은 뺐다 (2026-09-24 결정). Apple Developer Program 연 $99 + 맥이
|
||||
// 필요해 비용 대비 실익이 없다고 판단. 아이폰은 웹 PWA(tetrig.pages.dev)로 커버한다.
|
||||
// 구조는 commonMain 중심 그대로라, 나중에 iosTarget 만 다시 추가하면 된다.
|
||||
|
||||
sourceSets {
|
||||
commonMain.dependencies {
|
||||
|
||||
@@ -2,7 +2,7 @@ package kr.tkrmagid.tetrig.data
|
||||
|
||||
import androidx.compose.runtime.mutableStateMapOf
|
||||
|
||||
/** 플랫폼 키-값 저장소 (Android SharedPreferences / iOS NSUserDefaults). */
|
||||
/** 플랫폼 키-값 저장소 (Android 는 SharedPreferences). 기기 밖으로 나가지 않는다. */
|
||||
interface Store {
|
||||
fun getString(key: String, default: String): String
|
||||
fun putString(key: String, value: String)
|
||||
|
||||
@@ -64,18 +64,18 @@ fun MainScreen(
|
||||
ModeCard("솔로", "SPRINT · TIME ATTACK · ENDLESS", Palette.Cobalt, s) { showModes = true }
|
||||
}
|
||||
Box(Modifier.weight(1f)) {
|
||||
ModeCard("멀티 · 리그전", "실시간 1:1 랭크 매치 · 배치 10경기", Palette.Safety, s) { onSoon("멀티 · 리그전") }
|
||||
ModeCard("멀티 · 리그전", "실시간 1:1 랭크 매치 · 배치 10경기", Palette.Safety, s, soon = true) { onSoon("멀티 · 리그전") }
|
||||
}
|
||||
Box(Modifier.weight(1f)) {
|
||||
ModeCard("커스텀", "코드로 만들고 참가 · 인원 제한 없음", null, s) { onSoon("커스텀") }
|
||||
ModeCard("커스텀", "코드로 만들고 참가 · 인원 제한 없음", null, s, soon = true) { onSoon("커스텀") }
|
||||
}
|
||||
}
|
||||
} else {
|
||||
Box(Modifier.widthIn(max = 720.dp).align(Alignment.CenterHorizontally)) {
|
||||
Column {
|
||||
ModeCard("솔로", "SPRINT · TIME ATTACK · ENDLESS", Palette.Cobalt, s) { showModes = true }
|
||||
ModeCard("멀티 · 리그전", "실시간 1:1 랭크 매치 · 배치 10경기", Palette.Safety, s) { onSoon("멀티 · 리그전") }
|
||||
ModeCard("커스텀", "코드로 만들고 참가 · 인원 제한 없음", null, s) { onSoon("커스텀") }
|
||||
ModeCard("멀티 · 리그전", "실시간 1:1 랭크 매치 · 배치 10경기", Palette.Safety, s, soon = true) { onSoon("멀티 · 리그전") }
|
||||
ModeCard("커스텀", "코드로 만들고 참가 · 인원 제한 없음", null, s, soon = true) { onSoon("커스텀") }
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -144,6 +144,9 @@ private fun ModeCard(
|
||||
sub: String,
|
||||
barColor: Color?,
|
||||
scale: Float,
|
||||
// 아직 구현되지 않은 모드는 카드에 '준비 중' 배지를 달아 둔다. 스토어 스크린샷에
|
||||
// 없는 기능이 있는 것처럼 보이면 Play 정책(허위 표시) 위반이 된다.
|
||||
soon: Boolean = false,
|
||||
onClick: () -> Unit,
|
||||
) {
|
||||
Box(Modifier.fillMaxWidth().padding(bottom = (14 * scale).dp)) {
|
||||
@@ -159,7 +162,19 @@ private fun ModeCard(
|
||||
.weight(1f)
|
||||
.padding(start = (14 * scale).dp, top = (18 * scale).dp, end = 8.dp),
|
||||
) {
|
||||
Disp(title, size = (26 * scale).sp)
|
||||
Row(verticalAlignment = Alignment.CenterVertically) {
|
||||
Disp(title, size = (26 * scale).sp)
|
||||
if (soon) {
|
||||
Spacer(Modifier.width((8 * scale).dp))
|
||||
Box(
|
||||
Modifier
|
||||
.background(Palette.Sub)
|
||||
.padding(horizontal = (7 * scale).dp, vertical = (2 * scale).dp),
|
||||
) {
|
||||
Mono("준비 중", color = Palette.Bone, size = (11 * scale).sp, weight = FontWeight.Bold)
|
||||
}
|
||||
}
|
||||
}
|
||||
Spacer(Modifier.height(6.dp))
|
||||
Mono(sub, color = Palette.Sub, size = (12 * scale).sp)
|
||||
}
|
||||
|
||||
@@ -1,7 +0,0 @@
|
||||
package kr.tkrmagid.tetrig
|
||||
|
||||
import androidx.compose.ui.window.ComposeUIViewController
|
||||
import kr.tkrmagid.tetrig.ui.App
|
||||
|
||||
@Suppress("unused", "FunctionName")
|
||||
fun MainViewController() = ComposeUIViewController { App() }
|
||||
@@ -1,16 +0,0 @@
|
||||
package kr.tkrmagid.tetrig.data
|
||||
|
||||
import platform.Foundation.NSUserDefaults
|
||||
|
||||
private class IosStore : Store {
|
||||
private val defaults = NSUserDefaults.standardUserDefaults
|
||||
|
||||
override fun getString(key: String, default: String): String =
|
||||
defaults.stringForKey(key) ?: default
|
||||
|
||||
override fun putString(key: String, value: String) {
|
||||
defaults.setObject(value, forKey = key)
|
||||
}
|
||||
}
|
||||
|
||||
actual fun platformStore(): Store = IosStore()
|
||||
@@ -1,19 +0,0 @@
|
||||
import SwiftUI
|
||||
import UIKit
|
||||
import ComposeApp
|
||||
|
||||
struct ComposeView: UIViewControllerRepresentable {
|
||||
func makeUIViewController(context: Context) -> UIViewController {
|
||||
// Kotlin 쪽 MainViewController() — commonMain 의 App() 을 그대로 띄운다.
|
||||
MainViewControllerKt.MainViewController()
|
||||
}
|
||||
|
||||
func updateUIViewController(_ uiViewController: UIViewController, context: Context) {}
|
||||
}
|
||||
|
||||
struct ContentView: View {
|
||||
var body: some View {
|
||||
ComposeView()
|
||||
.ignoresSafeArea(.keyboard)
|
||||
}
|
||||
}
|
||||
@@ -1,11 +0,0 @@
|
||||
import SwiftUI
|
||||
|
||||
@main
|
||||
struct iOSApp: App {
|
||||
var body: some Scene {
|
||||
WindowGroup {
|
||||
ContentView()
|
||||
.ignoresSafeArea(.all)
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,53 +0,0 @@
|
||||
# XcodeGen 설정 — macOS 에서 `xcodegen generate` 로 iosApp.xcodeproj 를 만든다.
|
||||
# brew install xcodegen
|
||||
# cd app/iosApp && xcodegen generate && open iosApp.xcodeproj
|
||||
# Gradle 이 만든 ComposeApp.framework 를 링크한다.
|
||||
name: iosApp
|
||||
options:
|
||||
bundleIdPrefix: kr.tkrmagid
|
||||
deploymentTarget:
|
||||
iOS: "15.0"
|
||||
|
||||
settings:
|
||||
base:
|
||||
PRODUCT_BUNDLE_IDENTIFIER: kr.tkrmagid.tetrig
|
||||
MARKETING_VERSION: "2.0.0"
|
||||
CURRENT_PROJECT_VERSION: "2"
|
||||
SWIFT_VERSION: "5.0"
|
||||
TARGETED_DEVICE_FAMILY: "1,2" # iPhone + iPad
|
||||
ENABLE_USER_SCRIPT_SANDBOXING: NO
|
||||
|
||||
targets:
|
||||
iosApp:
|
||||
type: application
|
||||
platform: iOS
|
||||
sources:
|
||||
- path: iosApp
|
||||
info:
|
||||
path: iosApp/Info.plist
|
||||
properties:
|
||||
CFBundleDisplayName: TETRIG
|
||||
UILaunchScreen: {}
|
||||
UISupportedInterfaceOrientations:
|
||||
- UIInterfaceOrientationPortrait
|
||||
- UIInterfaceOrientationLandscapeLeft
|
||||
- UIInterfaceOrientationLandscapeRight
|
||||
UISupportedInterfaceOrientations~ipad:
|
||||
- UIInterfaceOrientationPortrait
|
||||
- UIInterfaceOrientationPortraitUpsideDown
|
||||
- UIInterfaceOrientationLandscapeLeft
|
||||
- UIInterfaceOrientationLandscapeRight
|
||||
UIRequiresFullScreen: false
|
||||
settings:
|
||||
base:
|
||||
FRAMEWORK_SEARCH_PATHS:
|
||||
- "$(SRCROOT)/../composeApp/build/xcode-frameworks/$(CONFIGURATION)/$(SDK_NAME)"
|
||||
OTHER_LDFLAGS:
|
||||
- "-framework"
|
||||
- "ComposeApp"
|
||||
preBuildScripts:
|
||||
- name: Build Compose framework
|
||||
script: |
|
||||
cd "$SRCROOT/../"
|
||||
./gradlew :composeApp:embedAndSignAppleFrameworkForXcode
|
||||
basedOnDependencyAnalysis: false
|
||||
Reference in New Issue
Block a user