[compose multiplatform 中級チュートリアル2]ページ遷移のやり方[compose multiplatform]

# 前回
[前回](https://qiita.com/shunk0616/private/506d8611d62ae7ecfaad)はアプリ作成の準備でした。

# ページ遷移させる
せっかくログイン画面がついているので残します。
widgetで選択したライブラリはvoyagerのものだったので使い方を調べると良い記事発見。
https://zenn.dev/turtlekazu/articles/f300809a2c0edb

## 骨組みを作る
まずは、ログイン画面とリスト画面の二つのファイルだけ作っちゃいます。

ListScreen
```
package org.company.saikyotodo.List

import androidx.compose.material3.Text
import androidx.compose.runtime.Composable
import cafe.adriel.voyager.core.model.rememberScreenModel
import cafe.adriel.voyager.core.screen.Screen

class ListScreen(): Screen {
    @Composable
    override fun Content() {
        val viewModel = rememberScreenModel { ListScreenModel() }
        Text("タスク一覧を表示")
    }
}
```
ListScreenModel
```
package org.company.saikyotodo.List

import cafe.adriel.voyager.core.model.ScreenModel

class ListScreenModel(): ScreenModel {
    // 処理
}
```
LoginScreen
```
package org.company.saikyotodo.Login

import androidx.compose.material3.Text
import androidx.compose.runtime.Composable
import cafe.adriel.voyager.core.model.rememberScreenModel
import cafe.adriel.voyager.core.screen.Screen

class LoginScreen(): Screen {
    @Composable
    override fun Content() {
        val viewModel = rememberScreenModel { LoginScreenModel() }
        Text("ログイン画面")
    }
}
```
LoginScreenModel
```
package org.company.saikyotodo.Login

import cafe.adriel.voyager.core.model.ScreenModel

class LoginScreenModel(): ScreenModel {
}
```

# App.ktをログイン画面に移す
まずはLoginScreen.ktに元のApp.ktを移します。
LoginScreen.kt
```
package org.company.saikyotodo.Login

import androidx.compose.foundation.layout.Arrangement
import androidx.compose.foundation.layout.Column
import androidx.compose.foundation.layout.Row
import androidx.compose.foundation.layout.Spacer
import androidx.compose.foundation.layout.WindowInsets
import androidx.compose.foundation.layout.fillMaxSize
import androidx.compose.foundation.layout.fillMaxWidth
import androidx.compose.foundation.layout.padding
import androidx.compose.foundation.layout.safeDrawing
import androidx.compose.foundation.layout.size
import androidx.compose.foundation.layout.windowInsetsPadding
import androidx.compose.foundation.text.KeyboardOptions
import androidx.compose.material.icons.Icons
import androidx.compose.material.icons.filled.Close
import androidx.compose.material.icons.filled.DarkMode
import androidx.compose.material.icons.filled.Edit
import androidx.compose.material.icons.filled.LightMode
import androidx.compose.material3.Button
import androidx.compose.material3.Icon
import androidx.compose.material3.IconButton
import androidx.compose.material3.MaterialTheme
import androidx.compose.material3.OutlinedTextField
import androidx.compose.material3.Text
import androidx.compose.runtime.Composable
import androidx.compose.runtime.getValue
import androidx.compose.runtime.mutableStateOf
import androidx.compose.runtime.remember
import androidx.compose.runtime.setValue
import androidx.compose.ui.Modifier
import androidx.compose.ui.text.input.KeyboardType
import androidx.compose.ui.text.input.PasswordVisualTransformation
import androidx.compose.ui.text.input.VisualTransformation
import androidx.compose.ui.unit.dp
import cafe.adriel.voyager.core.model.rememberScreenModel
import cafe.adriel.voyager.core.screen.Screen
import org.company.saikyotodo.List.ListScreen
import org.company.saikyotodo.theme.LocalThemeIsDark

class LoginScreen(): Screen {
    @Composable
    override fun Content() {
        val viewModel = rememberScreenModel { LoginScreenModel() }
        var email by remember { mutableStateOf("") }
        var password by remember { mutableStateOf("") }
        var passwordVisibility by remember { mutableStateOf(false) }

        Column(modifier = Modifier.fillMaxSize().windowInsetsPadding(WindowInsets.safeDrawing)) {

            Row(
                horizontalArrangement = Arrangement.Center
            ) {
                Text(
                    text = "Login",
                    style = MaterialTheme.typography.titleMedium,
                    modifier = Modifier.padding(16.dp)
                )

                Spacer(modifier = Modifier.weight(1.0f))

                var isDark by LocalThemeIsDark.current
                IconButton(
                    onClick = { isDark = !isDark }
                ) {
                    Icon(
                        modifier = Modifier.padding(8.dp).size(20.dp),
                        imageVector = if (isDark) Icons.Default.LightMode else Icons.Default.DarkMode,
                        contentDescription = null
                    )
                }
            }

            OutlinedTextField(
                value = email,
                onValueChange = { email = it },
                label = { Text("Email") },
                singleLine = true,
                modifier = Modifier.fillMaxWidth().padding(16.dp)
            )

            OutlinedTextField(
                value = password,
                onValueChange = { password = it },
                label = { Text("Password") },
                singleLine = true,
                visualTransformation = if (passwordVisibility) VisualTransformation.None else PasswordVisualTransformation(),
                modifier = Modifier.fillMaxWidth().padding(16.dp),
                keyboardOptions = KeyboardOptions(
                    keyboardType = KeyboardType.Password
                ),
                trailingIcon = {
                    IconButton(onClick = { passwordVisibility = !passwordVisibility }) {
                        val imageVector = if (passwordVisibility) Icons.Default.Close else Icons.Default.Edit
                        Icon(imageVector, contentDescription = if (passwordVisibility) "Hide password" else "Show password")
                    }
                }
            )

            Button(
                onClick = { /* Handle login logic here */ },
                modifier = Modifier.fillMaxWidth().padding(16.dp)
            ) {
                Text("Login")
            }
        }
    }
}
```

次にApp.ktの処理を削除して、最初はLoginScreenを表示するように修正

App.kt
```
package org.company.saikyotodo

import androidx.compose.foundation.layout.Arrangement
import androidx.compose.foundation.layout.Column
import androidx.compose.foundation.layout.Row
import androidx.compose.foundation.layout.Spacer
import androidx.compose.foundation.layout.WindowInsets
import androidx.compose.foundation.layout.fillMaxSize
import androidx.compose.foundation.layout.fillMaxWidth
import androidx.compose.foundation.layout.padding
import androidx.compose.foundation.layout.safeDrawing
import androidx.compose.foundation.layout.size
import androidx.compose.foundation.layout.windowInsetsPadding
import androidx.compose.foundation.text.KeyboardOptions
import androidx.compose.material.icons.Icons
import androidx.compose.material.icons.filled.Close
import androidx.compose.material.icons.filled.DarkMode
import androidx.compose.material.icons.filled.Edit
import androidx.compose.material.icons.filled.LightMode
import androidx.compose.material3.Button
import androidx.compose.material3.Icon
import androidx.compose.material3.IconButton
import androidx.compose.material3.MaterialTheme
import androidx.compose.material3.OutlinedTextField
import androidx.compose.material3.Surface
import androidx.compose.material3.Text
import androidx.compose.material3.TextButton
import androidx.compose.runtime.Composable
import androidx.compose.runtime.getValue
import androidx.compose.runtime.mutableStateOf
import androidx.compose.runtime.remember
import androidx.compose.runtime.setValue
import androidx.compose.ui.Modifier
import androidx.compose.ui.text.input.KeyboardType
import androidx.compose.ui.text.input.PasswordVisualTransformation
import androidx.compose.ui.text.input.VisualTransformation
import androidx.compose.ui.unit.dp
import cafe.adriel.voyager.navigator.Navigator
import org.company.saikyotodo.List.ListScreen
import org.company.saikyotodo.Login.LoginScreen
import org.company.saikyotodo.theme.AppTheme
import org.company.saikyotodo.theme.LocalThemeIsDark

@Composable
internal fun App() = AppTheme {
    Surface(
        modifier = Modifier.fillMaxSize(),
        color = MaterialTheme.colorScheme.background
    ) {
        val screens = listOf(LoginScreen())
        Navigator(screens)
    }
}

internal expect fun openUrl(url: String?)
```

# Login画面からList画面への遷移を定義する
LoginScreen.ktの中に画面遷移用のnavigatorを定義
```
~~~
override fun Content() {
    val viewModel = rememberScreenModel { LoginScreenModel() }
    var email by remember { mutableStateOf("") }
    var password by remember { mutableStateOf("") }
    var passwordVisibility by remember { mutableStateOf(false) }
    val navigator = LocalNavigator.currentOrThrow

    Column(modifier = Modifier.fillMaxSize().windowInsetsPadding(WindowInsets.safeDrawing)) {
~~~
```

ログインボタンのクリック処理として、List画面への遷移を定義
```
Button(
    onClick = {
        navigator.push(ListScreen())
    },
    modifier = Modifier.fillMaxWidth().padding(16.dp)
) {
    Text("Login")
}
```

以上です。

# 次
[次の記事](https://qiita.com/shunk0616/private/8fe95658ab2f64aea302)はアプリの見た目作成です。