Add compose dependencies for Android and empty NetplaySetupActivity
Derive compose colour theming from the existing XML styles already set at the activity level.
This commit is contained in:
1 parent
7b69949cec
commit
c716c32ce8
9 files changed
+252
No files matched your search
@@ -1,5 +1,6 @@
|
|||||||
plugins {
|
plugins {
|
||||||
alias(libs.plugins.android.application)
|
alias(libs.plugins.android.application)
|
||||||
|
alias(libs.plugins.kotlin.compose)
|
||||||
alias(libs.plugins.kotlin.serialization)
|
alias(libs.plugins.kotlin.serialization)
|
||||||
alias(libs.plugins.androidx.baselineprofile)
|
alias(libs.plugins.androidx.baselineprofile)
|
||||||
}
|
}
|
||||||
@@ -10,6 +11,7 @@ android {
|
|||||||
ndkVersion = "29.0.14206865"
|
ndkVersion = "29.0.14206865"
|
||||||
|
|
||||||
buildFeatures {
|
buildFeatures {
|
||||||
|
compose = true
|
||||||
viewBinding = true
|
viewBinding = true
|
||||||
buildConfig = true
|
buildConfig = true
|
||||||
resValues = true
|
resValues = true
|
||||||
@@ -150,6 +152,15 @@ dependencies {
|
|||||||
implementation(libs.kotlinx.coroutines.android)
|
implementation(libs.kotlinx.coroutines.android)
|
||||||
|
|
||||||
implementation(libs.filepicker)
|
implementation(libs.filepicker)
|
||||||
|
|
||||||
|
// Jetpack Compose
|
||||||
|
implementation(platform(libs.androidx.compose.bom))
|
||||||
|
implementation(libs.androidx.activity.compose)
|
||||||
|
implementation(libs.androidx.compose.material.icons)
|
||||||
|
implementation(libs.androidx.compose.material3)
|
||||||
|
implementation(libs.androidx.compose.ui)
|
||||||
|
debugImplementation(libs.androidx.compose.ui.tooling)
|
||||||
|
debugImplementation(libs.androidx.compose.ui.tooling.preview)
|
||||||
}
|
}
|
||||||
|
|
||||||
fun getGitVersion(): String {
|
fun getGitVersion(): String {
|
||||||
|
|||||||
@@ -133,6 +133,11 @@
|
|||||||
android:label="@string/user_data_submenu"
|
android:label="@string/user_data_submenu"
|
||||||
android:theme="@style/Theme.Dolphin.Main" />
|
android:theme="@style/Theme.Dolphin.Main" />
|
||||||
|
|
||||||
|
<activity
|
||||||
|
android:name=".features.netplay.ui.NetplaySetupActivity"
|
||||||
|
android:exported="false"
|
||||||
|
android:theme="@style/Theme.Dolphin.Main" />
|
||||||
|
|
||||||
<activity
|
<activity
|
||||||
android:name=".features.riivolution.ui.RiivolutionBootActivity"
|
android:name=".features.riivolution.ui.RiivolutionBootActivity"
|
||||||
android:exported="false"
|
android:exported="false"
|
||||||
|
|||||||
+107
@@ -0,0 +1,107 @@
|
|||||||
|
// SPDX-License-Identifier: GPL-2.0-or-later
|
||||||
|
|
||||||
|
@file:OptIn(ExperimentalMaterial3Api::class)
|
||||||
|
|
||||||
|
package org.dolphinemu.dolphinemu.features.netplay.ui
|
||||||
|
|
||||||
|
import android.content.Context
|
||||||
|
import android.content.Intent
|
||||||
|
import android.os.Bundle
|
||||||
|
import androidx.activity.compose.setContent
|
||||||
|
import androidx.activity.enableEdgeToEdge
|
||||||
|
import androidx.appcompat.app.AppCompatActivity
|
||||||
|
import androidx.compose.foundation.layout.Column
|
||||||
|
import androidx.compose.foundation.layout.consumeWindowInsets
|
||||||
|
import androidx.compose.foundation.layout.padding
|
||||||
|
import androidx.compose.foundation.rememberScrollState
|
||||||
|
import androidx.compose.foundation.verticalScroll
|
||||||
|
import androidx.compose.material.icons.Icons
|
||||||
|
import androidx.compose.material.icons.automirrored.filled.ArrowBack
|
||||||
|
import androidx.compose.material3.ExperimentalMaterial3Api
|
||||||
|
import androidx.compose.material3.Icon
|
||||||
|
import androidx.compose.material3.IconButton
|
||||||
|
import androidx.compose.material3.MaterialTheme
|
||||||
|
import androidx.compose.material3.MediumTopAppBar
|
||||||
|
import androidx.compose.material3.Scaffold
|
||||||
|
import androidx.compose.material3.Text
|
||||||
|
import androidx.compose.runtime.Composable
|
||||||
|
import androidx.compose.ui.Modifier
|
||||||
|
import androidx.compose.ui.res.stringResource
|
||||||
|
import androidx.compose.ui.tooling.preview.Preview
|
||||||
|
import org.dolphinemu.dolphinemu.R
|
||||||
|
import org.dolphinemu.dolphinemu.ui.main.ThemeProvider
|
||||||
|
import org.dolphinemu.dolphinemu.ui.theme.DolphinTheme
|
||||||
|
import org.dolphinemu.dolphinemu.utils.ThemeHelper
|
||||||
|
|
||||||
|
class NetplaySetupActivity : AppCompatActivity(), ThemeProvider {
|
||||||
|
override var themeId: Int = 0
|
||||||
|
|
||||||
|
override fun onCreate(savedInstanceState: Bundle?) {
|
||||||
|
ThemeHelper.setTheme(this)
|
||||||
|
enableEdgeToEdge()
|
||||||
|
super.onCreate(savedInstanceState)
|
||||||
|
|
||||||
|
setContent {
|
||||||
|
DolphinTheme {
|
||||||
|
NetplaySetupScreen(
|
||||||
|
onBackClicked = { finish() },
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun setTheme(themeId: Int) {
|
||||||
|
super.setTheme(themeId)
|
||||||
|
this.themeId = themeId
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun onResume() {
|
||||||
|
ThemeHelper.setCorrectTheme(this)
|
||||||
|
super.onResume()
|
||||||
|
}
|
||||||
|
|
||||||
|
companion object {
|
||||||
|
@JvmStatic
|
||||||
|
fun launch(context: Context) {
|
||||||
|
context.startActivity(Intent(context, NetplaySetupActivity::class.java))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Composable
|
||||||
|
private fun NetplaySetupScreen(
|
||||||
|
onBackClicked: () -> Unit,
|
||||||
|
) {
|
||||||
|
Scaffold(
|
||||||
|
topBar = {
|
||||||
|
MediumTopAppBar(
|
||||||
|
title = { Text(stringResource(R.string.netplay_setup_title)) },
|
||||||
|
navigationIcon = {
|
||||||
|
IconButton(onClick = onBackClicked) {
|
||||||
|
Icon(
|
||||||
|
Icons.AutoMirrored.Filled.ArrowBack,
|
||||||
|
contentDescription = "Back"
|
||||||
|
)
|
||||||
|
}
|
||||||
|
},
|
||||||
|
)
|
||||||
|
}
|
||||||
|
) { innerPadding ->
|
||||||
|
Column(
|
||||||
|
modifier = Modifier
|
||||||
|
.consumeWindowInsets(innerPadding)
|
||||||
|
.padding(innerPadding)
|
||||||
|
.padding(horizontal = DolphinTheme.scaffoldPadding)
|
||||||
|
.verticalScroll(rememberScrollState()),
|
||||||
|
) {
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Preview
|
||||||
|
@Composable
|
||||||
|
private fun NetplaySetupScreenPreview() {
|
||||||
|
MaterialTheme {
|
||||||
|
NetplayScreen(onBackClicked = {})
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -21,6 +21,7 @@ import org.dolphinemu.dolphinemu.features.settings.ui.MenuTag
|
|||||||
import org.dolphinemu.dolphinemu.features.sysupdate.ui.SystemMenuNotInstalledDialogFragment
|
import org.dolphinemu.dolphinemu.features.sysupdate.ui.SystemMenuNotInstalledDialogFragment
|
||||||
import org.dolphinemu.dolphinemu.features.sysupdate.ui.SystemUpdateProgressBarDialogFragment
|
import org.dolphinemu.dolphinemu.features.sysupdate.ui.SystemUpdateProgressBarDialogFragment
|
||||||
import org.dolphinemu.dolphinemu.features.sysupdate.ui.SystemUpdateViewModel
|
import org.dolphinemu.dolphinemu.features.sysupdate.ui.SystemUpdateViewModel
|
||||||
|
import org.dolphinemu.dolphinemu.features.netplay.ui.NetplaySetupActivity
|
||||||
import org.dolphinemu.dolphinemu.fragments.AboutDialogFragment
|
import org.dolphinemu.dolphinemu.fragments.AboutDialogFragment
|
||||||
import org.dolphinemu.dolphinemu.model.GameFileCache
|
import org.dolphinemu.dolphinemu.model.GameFileCache
|
||||||
import org.dolphinemu.dolphinemu.services.GameFileCacheManager
|
import org.dolphinemu.dolphinemu.services.GameFileCacheManager
|
||||||
@@ -188,6 +189,11 @@ class MainPresenter(private val mainView: MainView, private val activity: Fragme
|
|||||||
true
|
true
|
||||||
}
|
}
|
||||||
|
|
||||||
|
R.id.menu_netplay -> {
|
||||||
|
NetplaySetupActivity.launch(activity)
|
||||||
|
true
|
||||||
|
}
|
||||||
|
|
||||||
R.id.menu_about -> {
|
R.id.menu_about -> {
|
||||||
showAboutDialog()
|
showAboutDialog()
|
||||||
false
|
false
|
||||||
|
|||||||
@@ -0,0 +1,103 @@
|
|||||||
|
// SPDX-License-Identifier: GPL-2.0-or-later
|
||||||
|
|
||||||
|
package org.dolphinemu.dolphinemu.ui.theme
|
||||||
|
|
||||||
|
import android.content.Context
|
||||||
|
import androidx.annotation.AttrRes
|
||||||
|
import androidx.compose.foundation.isSystemInDarkTheme
|
||||||
|
import androidx.compose.material3.ColorScheme
|
||||||
|
import androidx.compose.material3.MaterialTheme
|
||||||
|
import androidx.compose.material3.darkColorScheme
|
||||||
|
import androidx.compose.material3.lightColorScheme
|
||||||
|
import androidx.compose.runtime.Composable
|
||||||
|
import androidx.compose.runtime.remember
|
||||||
|
import androidx.compose.ui.graphics.Color
|
||||||
|
import androidx.compose.ui.platform.LocalContext
|
||||||
|
import androidx.compose.ui.unit.dp
|
||||||
|
import com.google.android.material.color.MaterialColors
|
||||||
|
import androidx.appcompat.R as AppCompatR
|
||||||
|
import com.google.android.material.R as MaterialR
|
||||||
|
|
||||||
|
object DolphinTheme {
|
||||||
|
val scaffoldPadding = 16.dp
|
||||||
|
}
|
||||||
|
|
||||||
|
@Composable
|
||||||
|
fun DolphinTheme(content: @Composable () -> Unit) {
|
||||||
|
val context = LocalContext.current
|
||||||
|
val isDark = isSystemInDarkTheme()
|
||||||
|
val colorScheme = remember(context, isDark) { context.toDolphinColorScheme(isDark) }
|
||||||
|
|
||||||
|
MaterialTheme(
|
||||||
|
colorScheme = colorScheme,
|
||||||
|
content = content
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun Context.toDolphinColorScheme(isDark: Boolean): ColorScheme {
|
||||||
|
fun attr(@AttrRes attr: Int) = Color(MaterialColors.getColor(this, attr, 0))
|
||||||
|
|
||||||
|
val background = obtainStyledAttributes(intArrayOf(android.R.attr.colorBackground)).use {
|
||||||
|
Color(it.getColor(0, 0))
|
||||||
|
}
|
||||||
|
|
||||||
|
return if (isDark) {
|
||||||
|
darkColorScheme(
|
||||||
|
primary = attr(AppCompatR.attr.colorPrimary),
|
||||||
|
onPrimary = attr(MaterialR.attr.colorOnPrimary),
|
||||||
|
primaryContainer = attr(MaterialR.attr.colorPrimaryContainer),
|
||||||
|
onPrimaryContainer = attr(MaterialR.attr.colorOnPrimaryContainer),
|
||||||
|
secondary = attr(MaterialR.attr.colorSecondary),
|
||||||
|
onSecondary = attr(MaterialR.attr.colorOnSecondary),
|
||||||
|
secondaryContainer = attr(MaterialR.attr.colorSecondaryContainer),
|
||||||
|
onSecondaryContainer = attr(MaterialR.attr.colorOnSecondaryContainer),
|
||||||
|
tertiary = attr(MaterialR.attr.colorTertiary),
|
||||||
|
onTertiary = attr(MaterialR.attr.colorOnTertiary),
|
||||||
|
tertiaryContainer = attr(MaterialR.attr.colorTertiaryContainer),
|
||||||
|
onTertiaryContainer = attr(MaterialR.attr.colorOnTertiaryContainer),
|
||||||
|
error = attr(AppCompatR.attr.colorError),
|
||||||
|
onError = attr(MaterialR.attr.colorOnError),
|
||||||
|
errorContainer = attr(MaterialR.attr.colorErrorContainer),
|
||||||
|
onErrorContainer = attr(MaterialR.attr.colorOnErrorContainer),
|
||||||
|
background = background,
|
||||||
|
onBackground = attr(MaterialR.attr.colorOnBackground),
|
||||||
|
surface = attr(MaterialR.attr.colorSurface),
|
||||||
|
onSurface = attr(MaterialR.attr.colorOnSurface),
|
||||||
|
surfaceVariant = attr(MaterialR.attr.colorSurfaceVariant),
|
||||||
|
onSurfaceVariant = attr(MaterialR.attr.colorOnSurfaceVariant),
|
||||||
|
outline = attr(MaterialR.attr.colorOutline),
|
||||||
|
inverseSurface = attr(MaterialR.attr.colorSurfaceInverse),
|
||||||
|
inverseOnSurface = attr(MaterialR.attr.colorOnSurfaceInverse),
|
||||||
|
inversePrimary = attr(MaterialR.attr.colorPrimaryInverse),
|
||||||
|
)
|
||||||
|
} else {
|
||||||
|
lightColorScheme(
|
||||||
|
primary = attr(AppCompatR.attr.colorPrimary),
|
||||||
|
onPrimary = attr(MaterialR.attr.colorOnPrimary),
|
||||||
|
primaryContainer = attr(MaterialR.attr.colorPrimaryContainer),
|
||||||
|
onPrimaryContainer = attr(MaterialR.attr.colorOnPrimaryContainer),
|
||||||
|
secondary = attr(MaterialR.attr.colorSecondary),
|
||||||
|
onSecondary = attr(MaterialR.attr.colorOnSecondary),
|
||||||
|
secondaryContainer = attr(MaterialR.attr.colorSecondaryContainer),
|
||||||
|
onSecondaryContainer = attr(MaterialR.attr.colorOnSecondaryContainer),
|
||||||
|
tertiary = attr(MaterialR.attr.colorTertiary),
|
||||||
|
onTertiary = attr(MaterialR.attr.colorOnTertiary),
|
||||||
|
tertiaryContainer = attr(MaterialR.attr.colorTertiaryContainer),
|
||||||
|
onTertiaryContainer = attr(MaterialR.attr.colorOnTertiaryContainer),
|
||||||
|
error = attr(AppCompatR.attr.colorError),
|
||||||
|
onError = attr(MaterialR.attr.colorOnError),
|
||||||
|
errorContainer = attr(MaterialR.attr.colorErrorContainer),
|
||||||
|
onErrorContainer = attr(MaterialR.attr.colorOnErrorContainer),
|
||||||
|
background = background,
|
||||||
|
onBackground = attr(MaterialR.attr.colorOnBackground),
|
||||||
|
surface = attr(MaterialR.attr.colorSurface),
|
||||||
|
onSurface = attr(MaterialR.attr.colorOnSurface),
|
||||||
|
surfaceVariant = attr(MaterialR.attr.colorSurfaceVariant),
|
||||||
|
onSurfaceVariant = attr(MaterialR.attr.colorOnSurfaceVariant),
|
||||||
|
outline = attr(MaterialR.attr.colorOutline),
|
||||||
|
inverseSurface = attr(MaterialR.attr.colorSurfaceInverse),
|
||||||
|
inverseOnSurface = attr(MaterialR.attr.colorOnSurfaceInverse),
|
||||||
|
inversePrimary = attr(MaterialR.attr.colorPrimaryInverse),
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -51,6 +51,11 @@
|
|||||||
android:title="@string/grid_menu_online_system_update"
|
android:title="@string/grid_menu_online_system_update"
|
||||||
app:showAsAction="never"/>
|
app:showAsAction="never"/>
|
||||||
|
|
||||||
|
<item
|
||||||
|
android:id="@+id/menu_netplay"
|
||||||
|
android:title="@string/grid_menu_netplay"
|
||||||
|
app:showAsAction="never"/>
|
||||||
|
|
||||||
<item
|
<item
|
||||||
android:id="@+id/menu_about"
|
android:id="@+id/menu_about"
|
||||||
android:title="@string/grid_menu_about"
|
android:title="@string/grid_menu_about"
|
||||||
|
|||||||
@@ -495,6 +495,7 @@
|
|||||||
<string name="grid_menu_import_wii_save">Import Wii Save</string>
|
<string name="grid_menu_import_wii_save">Import Wii Save</string>
|
||||||
<string name="grid_menu_import_nand_backup">Import BootMii NAND Backup</string>
|
<string name="grid_menu_import_nand_backup">Import BootMii NAND Backup</string>
|
||||||
<string name="grid_menu_online_system_update">Perform Online System Update</string>
|
<string name="grid_menu_online_system_update">Perform Online System Update</string>
|
||||||
|
<string name="grid_menu_netplay">Netplay</string>
|
||||||
<string name="grid_menu_load_wii_system_menu">Load Wii System Menu</string>
|
<string name="grid_menu_load_wii_system_menu">Load Wii System Menu</string>
|
||||||
<string name="grid_menu_load_wii_system_menu_installed">Load Wii System Menu (%s)</string>
|
<string name="grid_menu_load_wii_system_menu_installed">Load Wii System Menu (%s)</string>
|
||||||
<string name="grid_menu_load_vwii_system_menu_installed">Load vWii System Menu (%s)</string>
|
<string name="grid_menu_load_vwii_system_menu_installed">Load vWii System Menu (%s)</string>
|
||||||
@@ -980,4 +981,8 @@ It can efficiently compress both junk data and encrypted Wii data.
|
|||||||
<string name="achievements_logout">Log Out</string>
|
<string name="achievements_logout">Log Out</string>
|
||||||
<string name="achievements_login_in_progress">Logging In</string>
|
<string name="achievements_login_in_progress">Logging In</string>
|
||||||
<string name="achievements_login_failed">Login Failed</string>
|
<string name="achievements_login_failed">Login Failed</string>
|
||||||
|
|
||||||
|
<!-- Netplay -->
|
||||||
|
<string name="netplay_setup_title">Netplay Setup</string>
|
||||||
|
<string name="netplay_nickname_label">Nickname</string>
|
||||||
</resources>
|
</resources>
|
||||||
@@ -4,6 +4,7 @@ plugins {
|
|||||||
alias(libs.plugins.android.library) apply false
|
alias(libs.plugins.android.library) apply false
|
||||||
alias(libs.plugins.android.test) apply false
|
alias(libs.plugins.android.test) apply false
|
||||||
alias(libs.plugins.androidx.baselineprofile) apply false
|
alias(libs.plugins.androidx.baselineprofile) apply false
|
||||||
|
alias(libs.plugins.kotlin.compose) apply false
|
||||||
}
|
}
|
||||||
|
|
||||||
buildscript {
|
buildscript {
|
||||||
|
|||||||
@@ -4,6 +4,7 @@ appcompat = "1.7.1"
|
|||||||
benchmarkMacroJunit4 = "1.5.0-alpha04"
|
benchmarkMacroJunit4 = "1.5.0-alpha04"
|
||||||
cardview = "1.0.0"
|
cardview = "1.0.0"
|
||||||
coil = "2.7.0"
|
coil = "2.7.0"
|
||||||
|
compose-bom = "2025.04.00"
|
||||||
constraintlayout = "2.2.1"
|
constraintlayout = "2.2.1"
|
||||||
coreKtx = "1.18.0"
|
coreKtx = "1.18.0"
|
||||||
coreSplashscreen = "1.2.0"
|
coreSplashscreen = "1.2.0"
|
||||||
@@ -27,9 +28,16 @@ tvprovider = "1.1.0"
|
|||||||
uiautomator = "2.3.0"
|
uiautomator = "2.3.0"
|
||||||
|
|
||||||
[libraries]
|
[libraries]
|
||||||
|
androidx-activity-compose = { group = "androidx.activity", name = "activity-compose" }
|
||||||
androidx-appcompat = { group = "androidx.appcompat", name = "appcompat", version.ref = "appcompat" }
|
androidx-appcompat = { group = "androidx.appcompat", name = "appcompat", version.ref = "appcompat" }
|
||||||
androidx-benchmark-macro-junit4 = { group = "androidx.benchmark", name = "benchmark-macro-junit4", version.ref = "benchmarkMacroJunit4" }
|
androidx-benchmark-macro-junit4 = { group = "androidx.benchmark", name = "benchmark-macro-junit4", version.ref = "benchmarkMacroJunit4" }
|
||||||
androidx-cardview = { group = "androidx.cardview", name = "cardview", version.ref = "cardview" }
|
androidx-cardview = { group = "androidx.cardview", name = "cardview", version.ref = "cardview" }
|
||||||
|
androidx-compose-bom = { group = "androidx.compose", name = "compose-bom", version.ref = "compose-bom" }
|
||||||
|
androidx-compose-material-icons = { group = "androidx.compose.material", name = "material-icons-extended" }
|
||||||
|
androidx-compose-material3 = { group = "androidx.compose.material3", name = "material3" }
|
||||||
|
androidx-compose-ui = { group = "androidx.compose.ui", name = "ui" }
|
||||||
|
androidx-compose-ui-tooling = { group = "androidx.compose.ui", name = "ui-tooling" }
|
||||||
|
androidx-compose-ui-tooling-preview = { group = "androidx.compose.ui", name = "ui-tooling-preview" }
|
||||||
androidx-constraintlayout = { group = "androidx.constraintlayout", name = "constraintlayout", version.ref = "constraintlayout" }
|
androidx-constraintlayout = { group = "androidx.constraintlayout", name = "constraintlayout", version.ref = "constraintlayout" }
|
||||||
androidx-core-ktx = { group = "androidx.core", name = "core-ktx", version.ref = "coreKtx" }
|
androidx-core-ktx = { group = "androidx.core", name = "core-ktx", version.ref = "coreKtx" }
|
||||||
androidx-core-splashscreen = { group = "androidx.core", name = "core-splashscreen", version.ref = "coreSplashscreen" }
|
androidx-core-splashscreen = { group = "androidx.core", name = "core-splashscreen", version.ref = "coreSplashscreen" }
|
||||||
@@ -58,4 +66,5 @@ android-application = { id = "com.android.application", version.ref = "agp" }
|
|||||||
android-library = { id = "com.android.library", version.ref = "agp" }
|
android-library = { id = "com.android.library", version.ref = "agp" }
|
||||||
android-test = { id = "com.android.test", version.ref = "agp" }
|
android-test = { id = "com.android.test", version.ref = "agp" }
|
||||||
androidx-baselineprofile = { id = "androidx.baselineprofile", version.ref = "benchmarkMacroJunit4" }
|
androidx-baselineprofile = { id = "androidx.baselineprofile", version.ref = "benchmarkMacroJunit4" }
|
||||||
|
kotlin-compose = { id = "org.jetbrains.kotlin.plugin.compose", version.ref = "kotlin" }
|
||||||
kotlin-serialization = { id = "org.jetbrains.kotlin.plugin.serialization", version.ref = "kotlin" }
|
kotlin-serialization = { id = "org.jetbrains.kotlin.plugin.serialization", version.ref = "kotlin" }
|
||||||
Reference in new issue
Block a user