feat: wire up app root, navigation, and platform entry points
- Add App.kt root composable with state-based routing (Login -> Schedule -> EventDetail) driven by AuthViewModel.state - Update MainActivity (Android), MainViewController (iOS), and Main.kt (Desktop) to host App() with their platform OAuthClient - Add iOS Xcode source files (MyIceApp.swift, ContentView.swift) and Info.plist with myice:// URL scheme - Delete Placeholder.kt - Add kotlinx-coroutines-swing to desktopMain to provide Dispatchers.Main for viewModelScope (app crashed on desktop without it)
This commit is contained in:
@@ -59,6 +59,7 @@ kotlin {
|
|||||||
val desktopMain by getting {
|
val desktopMain by getting {
|
||||||
dependencies {
|
dependencies {
|
||||||
implementation(libs.ktor.client.cio)
|
implementation(libs.ktor.client.cio)
|
||||||
|
implementation(libs.kotlinx.coroutines.swing)
|
||||||
implementation(compose.desktop.currentOs)
|
implementation(compose.desktop.currentOs)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -4,14 +4,14 @@ import android.os.Bundle
|
|||||||
import androidx.activity.ComponentActivity
|
import androidx.activity.ComponentActivity
|
||||||
import androidx.activity.compose.setContent
|
import androidx.activity.compose.setContent
|
||||||
import androidx.activity.enableEdgeToEdge
|
import androidx.activity.enableEdgeToEdge
|
||||||
import androidx.compose.material3.Text
|
import ch.parano.myice.auth.AndroidOAuthClient
|
||||||
|
|
||||||
class MainActivity : ComponentActivity() {
|
class MainActivity : ComponentActivity() {
|
||||||
override fun onCreate(savedInstanceState: Bundle?) {
|
override fun onCreate(savedInstanceState: Bundle?) {
|
||||||
enableEdgeToEdge()
|
enableEdgeToEdge()
|
||||||
super.onCreate(savedInstanceState)
|
super.onCreate(savedInstanceState)
|
||||||
setContent {
|
setContent {
|
||||||
Text("MyIce")
|
App(oauthClient = AndroidOAuthClient(applicationContext))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -0,0 +1,77 @@
|
|||||||
|
package ch.parano.myice
|
||||||
|
|
||||||
|
import androidx.compose.runtime.Composable
|
||||||
|
import androidx.compose.runtime.LaunchedEffect
|
||||||
|
import androidx.compose.runtime.collectAsState
|
||||||
|
import androidx.compose.runtime.getValue
|
||||||
|
import androidx.compose.runtime.mutableStateOf
|
||||||
|
import androidx.compose.runtime.remember
|
||||||
|
import androidx.compose.runtime.setValue
|
||||||
|
import ch.parano.myice.auth.AuthSettings
|
||||||
|
import ch.parano.myice.auth.OAuthClient
|
||||||
|
import ch.parano.myice.cache.ScheduleCache
|
||||||
|
import ch.parano.myice.network.ApiService
|
||||||
|
import ch.parano.myice.network.createHttpClient
|
||||||
|
import ch.parano.myice.ui.screens.EventDetailScreen
|
||||||
|
import ch.parano.myice.ui.screens.LoginScreen
|
||||||
|
import ch.parano.myice.ui.screens.ScheduleScreen
|
||||||
|
import ch.parano.myice.ui.theme.MyIceTheme
|
||||||
|
import ch.parano.myice.viewmodel.AuthState
|
||||||
|
import ch.parano.myice.viewmodel.AuthViewModel
|
||||||
|
import ch.parano.myice.viewmodel.EventDetailViewModel
|
||||||
|
import ch.parano.myice.viewmodel.ScheduleViewModel
|
||||||
|
import com.russhwolf.settings.Settings
|
||||||
|
|
||||||
|
sealed class Screen {
|
||||||
|
data object Login : Screen()
|
||||||
|
data object Schedule : Screen()
|
||||||
|
data class EventDetail(val gameId: String, val account: String, val eventTitle: String) : Screen()
|
||||||
|
}
|
||||||
|
|
||||||
|
@Composable
|
||||||
|
fun App(
|
||||||
|
oauthClient: OAuthClient,
|
||||||
|
) {
|
||||||
|
val apiService = remember { ApiService(createHttpClient()) }
|
||||||
|
val authSettings = remember { AuthSettings(Settings()) }
|
||||||
|
val cache = remember { ScheduleCache(Settings()) }
|
||||||
|
|
||||||
|
val authViewModel = remember { AuthViewModel(apiService, authSettings, oauthClient) }
|
||||||
|
val scheduleViewModel = remember { ScheduleViewModel(apiService, cache, authSettings) }
|
||||||
|
val eventDetailViewModel = remember { EventDetailViewModel(apiService) }
|
||||||
|
|
||||||
|
var currentScreen by remember { mutableStateOf<Screen>(Screen.Login) }
|
||||||
|
|
||||||
|
LaunchedEffect(Unit) {
|
||||||
|
authViewModel.init()
|
||||||
|
}
|
||||||
|
|
||||||
|
val authState by authViewModel.state.collectAsState()
|
||||||
|
|
||||||
|
LaunchedEffect(authState) {
|
||||||
|
currentScreen = when (authState) {
|
||||||
|
is AuthState.Authenticated -> Screen.Schedule
|
||||||
|
is AuthState.Unauthenticated -> Screen.Login
|
||||||
|
AuthState.Loading -> currentScreen
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
MyIceTheme {
|
||||||
|
when (val screen = currentScreen) {
|
||||||
|
Screen.Login -> LoginScreen(viewModel = authViewModel)
|
||||||
|
Screen.Schedule -> ScheduleScreen(
|
||||||
|
scheduleViewModel = scheduleViewModel,
|
||||||
|
authViewModel = authViewModel,
|
||||||
|
onEventClick = { gameId, account, eventTitle ->
|
||||||
|
currentScreen = Screen.EventDetail(gameId, account, eventTitle)
|
||||||
|
},
|
||||||
|
)
|
||||||
|
is Screen.EventDetail -> EventDetailScreen(
|
||||||
|
gameId = screen.gameId,
|
||||||
|
account = screen.account,
|
||||||
|
eventTitle = screen.eventTitle,
|
||||||
|
viewModel = eventDetailViewModel,
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -1 +0,0 @@
|
|||||||
package ch.parano.myice
|
|
||||||
@@ -1,11 +1,11 @@
|
|||||||
package ch.parano.myice
|
package ch.parano.myice
|
||||||
|
|
||||||
import androidx.compose.material3.Text
|
|
||||||
import androidx.compose.ui.window.Window
|
import androidx.compose.ui.window.Window
|
||||||
import androidx.compose.ui.window.application
|
import androidx.compose.ui.window.application
|
||||||
|
import ch.parano.myice.auth.DesktopOAuthClient
|
||||||
|
|
||||||
fun main() = application {
|
fun main() = application {
|
||||||
Window(onCloseRequest = ::exitApplication, title = "MyIce") {
|
Window(onCloseRequest = ::exitApplication, title = "MyIce") {
|
||||||
Text("MyIce")
|
App(oauthClient = DesktopOAuthClient())
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,8 +1,8 @@
|
|||||||
package ch.parano.myice
|
package ch.parano.myice
|
||||||
|
|
||||||
import androidx.compose.material3.Text
|
|
||||||
import androidx.compose.ui.window.ComposeUIViewController
|
import androidx.compose.ui.window.ComposeUIViewController
|
||||||
|
import ch.parano.myice.auth.IosOAuthClient
|
||||||
|
|
||||||
fun MainViewController() = ComposeUIViewController {
|
fun MainViewController() = ComposeUIViewController {
|
||||||
Text("MyIce")
|
App(oauthClient = IosOAuthClient())
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -20,6 +20,7 @@ ktor-client-darwin = { module = "io.ktor:ktor-client-darwin", version.ref = "kto
|
|||||||
kotlinx-serialization-json = { module = "org.jetbrains.kotlinx:kotlinx-serialization-json", version.ref = "kotlinx-serialization-json" }
|
kotlinx-serialization-json = { module = "org.jetbrains.kotlinx:kotlinx-serialization-json", version.ref = "kotlinx-serialization-json" }
|
||||||
kotlinx-coroutines-core = { module = "org.jetbrains.kotlinx:kotlinx-coroutines-core", version.ref = "kotlinx-coroutines" }
|
kotlinx-coroutines-core = { module = "org.jetbrains.kotlinx:kotlinx-coroutines-core", version.ref = "kotlinx-coroutines" }
|
||||||
kotlinx-coroutines-test = { module = "org.jetbrains.kotlinx:kotlinx-coroutines-test", version.ref = "kotlinx-coroutines" }
|
kotlinx-coroutines-test = { module = "org.jetbrains.kotlinx:kotlinx-coroutines-test", version.ref = "kotlinx-coroutines" }
|
||||||
|
kotlinx-coroutines-swing = { module = "org.jetbrains.kotlinx:kotlinx-coroutines-swing", version.ref = "kotlinx-coroutines" }
|
||||||
kotlinx-datetime = { module = "org.jetbrains.kotlinx:kotlinx-datetime", version.ref = "kotlinx-datetime" }
|
kotlinx-datetime = { module = "org.jetbrains.kotlinx:kotlinx-datetime", version.ref = "kotlinx-datetime" }
|
||||||
multiplatform-settings = { module = "com.russhwolf:multiplatform-settings", version.ref = "multiplatform-settings" }
|
multiplatform-settings = { module = "com.russhwolf:multiplatform-settings", version.ref = "multiplatform-settings" }
|
||||||
multiplatform-settings-no-arg = { module = "com.russhwolf:multiplatform-settings-no-arg", version.ref = "multiplatform-settings" }
|
multiplatform-settings-no-arg = { module = "com.russhwolf:multiplatform-settings-no-arg", version.ref = "multiplatform-settings" }
|
||||||
|
|||||||
@@ -0,0 +1,10 @@
|
|||||||
|
import SwiftUI
|
||||||
|
import ComposeApp
|
||||||
|
|
||||||
|
struct ContentView: UIViewRepresentable {
|
||||||
|
func makeUIView(context: Context) -> some UIView {
|
||||||
|
MainViewControllerKt.MainViewController()
|
||||||
|
}
|
||||||
|
|
||||||
|
func updateUIView(_ uiView: UIViewType, context: Context) {}
|
||||||
|
}
|
||||||
@@ -0,0 +1,55 @@
|
|||||||
|
<?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>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>
|
||||||
|
<key>CFBundleURLTypes</key>
|
||||||
|
<array>
|
||||||
|
<dict>
|
||||||
|
<key>CFBundleURLName</key>
|
||||||
|
<string>ch.parano.myice</string>
|
||||||
|
<key>CFBundleURLSchemes</key>
|
||||||
|
<array>
|
||||||
|
<string>myice</string>
|
||||||
|
</array>
|
||||||
|
</dict>
|
||||||
|
</array>
|
||||||
|
</dict>
|
||||||
|
</plist>
|
||||||
@@ -0,0 +1,11 @@
|
|||||||
|
import SwiftUI
|
||||||
|
import ComposeApp
|
||||||
|
|
||||||
|
@main
|
||||||
|
struct MyIceApp: App {
|
||||||
|
var body: some Scene {
|
||||||
|
WindowGroup {
|
||||||
|
ContentView()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
Reference in New Issue
Block a user