diff --git a/core/common/src/main/kotlin/com/ninecraft/booket/core/common/extensions/Modifier.kt b/core/common/src/main/kotlin/com/ninecraft/booket/core/common/extensions/Modifier.kt index 7243d759..63401729 100644 --- a/core/common/src/main/kotlin/com/ninecraft/booket/core/common/extensions/Modifier.kt +++ b/core/common/src/main/kotlin/com/ninecraft/booket/core/common/extensions/Modifier.kt @@ -1,9 +1,9 @@ package com.ninecraft.booket.core.common.extensions import android.annotation.SuppressLint -import androidx.compose.foundation.LocalIndication import androidx.compose.foundation.clickable import androidx.compose.foundation.interaction.MutableInteractionSource +import androidx.compose.material3.ripple import androidx.compose.runtime.remember import androidx.compose.ui.Modifier import androidx.compose.ui.composed @@ -44,7 +44,7 @@ fun Modifier.clickableSingle( onClickLabel = onClickLabel, onClick = { multipleEventsCutter.processEvent { onClick() } }, role = role, - indication = LocalIndication.current, + indication = ripple(), interactionSource = remember { MutableInteractionSource() }, ) } diff --git a/core/designsystem/src/main/kotlin/com/ninecraft/booket/core/designsystem/component/appbar/ReedTopAppBar.kt b/core/designsystem/src/main/kotlin/com/ninecraft/booket/core/designsystem/component/appbar/ReedTopAppBar.kt index 42b03b88..daa6684c 100644 --- a/core/designsystem/src/main/kotlin/com/ninecraft/booket/core/designsystem/component/appbar/ReedTopAppBar.kt +++ b/core/designsystem/src/main/kotlin/com/ninecraft/booket/core/designsystem/component/appbar/ReedTopAppBar.kt @@ -62,7 +62,7 @@ fun ReedTopAppBar( text = title, modifier = Modifier.weight(1f), textAlign = TextAlign.Center, - style = ReedTheme.typography.heading2SemiBold, + style = ReedTheme.typography.headline2SemiBold, ) if (endIconRes != null) { @@ -87,14 +87,14 @@ fun ReedTopAppBar( fun ReedBackTopAppBar( modifier: Modifier = Modifier, title: String = "", - onNavigateBack: () -> Unit = {}, + onBackClick: () -> Unit = {}, ) { ReedTopAppBar( modifier = modifier, title = title, startIconRes = R.drawable.ic_chevron_left, startIconDescription = "Back", - startIconOnClick = onNavigateBack, + startIconOnClick = onBackClick, ) } diff --git a/feature/login/src/main/kotlin/com/ninecraft/booket/feature/termsagreement/TermsAgreementScreen.kt b/feature/login/src/main/kotlin/com/ninecraft/booket/feature/termsagreement/TermsAgreementScreen.kt index f4a9f058..ac42476e 100644 --- a/feature/login/src/main/kotlin/com/ninecraft/booket/feature/termsagreement/TermsAgreementScreen.kt +++ b/feature/login/src/main/kotlin/com/ninecraft/booket/feature/termsagreement/TermsAgreementScreen.kt @@ -50,7 +50,7 @@ internal fun TermsAgreement( .background(White), ) { ReedBackTopAppBar( - onNavigateBack = { + onBackClick = { state.eventSink(TermsAgreementUiEvent.OnBackClick) }, ) diff --git a/feature/settings/build.gradle.kts b/feature/settings/build.gradle.kts index 0df5174e..456eaf80 100644 --- a/feature/settings/build.gradle.kts +++ b/feature/settings/build.gradle.kts @@ -2,7 +2,7 @@ plugins { alias(libs.plugins.booket.android.feature) - alias(libs.plugins.kotlin.serialization) + alias(libs.plugins.booket.kotlin.library.serialization) alias(libs.plugins.kotlin.parcelize) } diff --git a/feature/settings/src/main/assets/oss_licenses.json b/feature/settings/src/main/assets/oss_licenses.json new file mode 100644 index 00000000..71d1421f --- /dev/null +++ b/feature/settings/src/main/assets/oss_licenses.json @@ -0,0 +1,57 @@ +[ + { + "name": "Circuit", + "license": "Apache License 2.0", + "url": "https://github.com/slackhq/circuit" + }, + { + "name": "Compose Effects", + "license": "Apache License 2.0", + "url": "https://github.com/skydoves/compose-effects" + }, + { + "name": "Compose Stable Marker", + "license": "Apache License 2.0", + "url": "https://github.com/skydoves/compose-stable-marker" + }, + { + "name": "Hilt", + "license": "Apache License 2.0", + "url": "https://dagger.dev/hilt/" + }, + { + "name": "Logger", + "license": "Apache License 2.0", + "url": "https://github.com/orhanobut/logger" + }, + { + "name": "OkHttp", + "license": "Apache License 2.0", + "url": "https://square.github.io/okhttp/" + }, + { + "name": "Retrofit", + "license": "Apache License 2.0", + "url": "https://square.github.io/retrofit/" + }, + { + "name": "OkHttp Logging Interceptor", + "license": "Apache License 2.0", + "url": "https://github.com/square/okhttp/tree/master/okhttp-logging-interceptor" + }, + { + "name": "Coil", + "license": "Apache License 2.0", + "url": "https://github.com/coil-kt/coil" + }, + { + "name": "Detekt", + "license": "Apache License 2.0", + "url": "https://github.com/detekt/detekt" + }, + { + "name": "ktlint", + "license": "MIT License", + "url": "https://github.com/pinterest/ktlint" + } +] diff --git a/feature/settings/src/main/kotlin/com/ninecraft/booket/feature/settings/SettingsPresenter.kt b/feature/settings/src/main/kotlin/com/ninecraft/booket/feature/settings/SettingsPresenter.kt index bac6a716..8d3bed49 100644 --- a/feature/settings/src/main/kotlin/com/ninecraft/booket/feature/settings/SettingsPresenter.kt +++ b/feature/settings/src/main/kotlin/com/ninecraft/booket/feature/settings/SettingsPresenter.kt @@ -4,6 +4,7 @@ import androidx.compose.runtime.Composable import androidx.compose.runtime.getValue import androidx.compose.runtime.mutableStateOf import androidx.compose.runtime.setValue +import com.ninecraft.booket.screens.OssLicensesScreen import com.ninecraft.booket.screens.SettingsScreen import com.slack.circuit.codegen.annotations.CircuitInject import com.slack.circuit.retained.rememberRetained @@ -34,6 +35,10 @@ class SettingsPresenter @AssistedInject constructor( // TODO: 웹뷰 화면으로 이동 } + is SettingsUiEvent.OnOssLicensesClick -> { + navigator.goTo(OssLicensesScreen) + } + is SettingsUiEvent.OnLogoutClick -> { isLogoutBottomSheetVisible = true } diff --git a/feature/settings/src/main/kotlin/com/ninecraft/booket/feature/settings/SettingsScreen.kt b/feature/settings/src/main/kotlin/com/ninecraft/booket/feature/settings/SettingsScreen.kt index 516f4f90..10c6e9aa 100644 --- a/feature/settings/src/main/kotlin/com/ninecraft/booket/feature/settings/SettingsScreen.kt +++ b/feature/settings/src/main/kotlin/com/ninecraft/booket/feature/settings/SettingsScreen.kt @@ -60,7 +60,7 @@ internal fun Settings( ) { ReedBackTopAppBar( title = stringResource(R.string.settings_title), - onNavigateBack = { + onBackClick = { state.eventSink(SettingsUiEvent.OnBackClick) }, ) @@ -95,7 +95,7 @@ internal fun Settings( SettingItem( title = stringResource(R.string.settings_open_source_license), onItemClick = { - state.eventSink(SettingsUiEvent.OnTermDetailClick("")) + state.eventSink(SettingsUiEvent.OnOssLicensesClick) }, action = { Icon( diff --git a/feature/settings/src/main/kotlin/com/ninecraft/booket/feature/settings/SettingsUiState.kt b/feature/settings/src/main/kotlin/com/ninecraft/booket/feature/settings/SettingsUiState.kt index 06dce37b..0b47776d 100644 --- a/feature/settings/src/main/kotlin/com/ninecraft/booket/feature/settings/SettingsUiState.kt +++ b/feature/settings/src/main/kotlin/com/ninecraft/booket/feature/settings/SettingsUiState.kt @@ -13,6 +13,7 @@ data class SettingsUiState( sealed interface SettingsUiEvent : CircuitUiEvent { data object OnBackClick : SettingsUiEvent data class OnTermDetailClick(val title: String) : SettingsUiEvent + data object OnOssLicensesClick : SettingsUiEvent data object OnLogoutClick : SettingsUiEvent data object OnWithdrawClick : SettingsUiEvent data object OnBottomSheetDismissed : SettingsUiEvent diff --git a/feature/settings/src/main/kotlin/com/ninecraft/booket/feature/settings/osslicenses/OssLicenseInfo.kt b/feature/settings/src/main/kotlin/com/ninecraft/booket/feature/settings/osslicenses/OssLicenseInfo.kt new file mode 100644 index 00000000..650b679d --- /dev/null +++ b/feature/settings/src/main/kotlin/com/ninecraft/booket/feature/settings/osslicenses/OssLicenseInfo.kt @@ -0,0 +1,10 @@ +package com.ninecraft.booket.feature.settings.osslicenses + +import kotlinx.serialization.Serializable + +@Serializable +data class OssLicenseInfo( + val name: String, + val license: String, + val url: String, +) diff --git a/feature/settings/src/main/kotlin/com/ninecraft/booket/feature/settings/osslicenses/OssLicensesPresenter.kt b/feature/settings/src/main/kotlin/com/ninecraft/booket/feature/settings/osslicenses/OssLicensesPresenter.kt new file mode 100644 index 00000000..0142dd21 --- /dev/null +++ b/feature/settings/src/main/kotlin/com/ninecraft/booket/feature/settings/osslicenses/OssLicensesPresenter.kt @@ -0,0 +1,35 @@ +package com.ninecraft.booket.feature.settings.osslicenses + +import androidx.compose.runtime.Composable +import com.ninecraft.booket.screens.OssLicensesScreen +import com.slack.circuit.codegen.annotations.CircuitInject +import com.slack.circuit.runtime.Navigator +import com.slack.circuit.runtime.presenter.Presenter +import dagger.assisted.Assisted +import dagger.assisted.AssistedFactory +import dagger.assisted.AssistedInject +import dagger.hilt.android.components.ActivityRetainedComponent + +class OssLicensesPresenter @AssistedInject constructor( + @Assisted val navigator: Navigator, +) : Presenter { + @Composable + override fun present(): OssLicensesUiState { + fun handleEvent(event: OssLicensesUiEvent) { + when (event) { + is OssLicensesUiEvent.OnBackClicked -> { + navigator.pop() + } + } + } + return OssLicensesUiState( + eventSink = ::handleEvent, + ) + } + + @CircuitInject(OssLicensesScreen::class, ActivityRetainedComponent::class) + @AssistedFactory + fun interface Factory { + fun create(navigator: Navigator): OssLicensesPresenter + } +} diff --git a/feature/settings/src/main/kotlin/com/ninecraft/booket/feature/settings/osslicenses/OssLicensesScreen.kt b/feature/settings/src/main/kotlin/com/ninecraft/booket/feature/settings/osslicenses/OssLicensesScreen.kt new file mode 100644 index 00000000..398fe6de --- /dev/null +++ b/feature/settings/src/main/kotlin/com/ninecraft/booket/feature/settings/osslicenses/OssLicensesScreen.kt @@ -0,0 +1,159 @@ +package com.ninecraft.booket.feature.settings.osslicenses + +import android.content.Context +import androidx.compose.foundation.background +import androidx.compose.foundation.layout.Box +import androidx.compose.foundation.layout.Column +import androidx.compose.foundation.layout.Row +import androidx.compose.foundation.layout.Spacer +import androidx.compose.foundation.layout.fillMaxSize +import androidx.compose.foundation.layout.fillMaxWidth +import androidx.compose.foundation.layout.height +import androidx.compose.foundation.layout.padding +import androidx.compose.foundation.layout.size +import androidx.compose.foundation.layout.width +import androidx.compose.foundation.lazy.LazyColumn +import androidx.compose.foundation.lazy.items +import androidx.compose.foundation.shape.CircleShape +import androidx.compose.foundation.shape.RoundedCornerShape +import androidx.compose.material3.Text +import androidx.compose.runtime.Composable +import androidx.compose.runtime.LaunchedEffect +import androidx.compose.runtime.getValue +import androidx.compose.runtime.mutableStateOf +import androidx.compose.runtime.remember +import androidx.compose.runtime.setValue +import androidx.compose.ui.Alignment +import androidx.compose.ui.Modifier +import androidx.compose.ui.platform.LocalContext +import androidx.compose.ui.res.stringResource +import androidx.compose.ui.text.font.FontWeight +import androidx.compose.ui.text.style.TextOverflow +import com.ninecraft.booket.core.designsystem.DevicePreview +import com.ninecraft.booket.core.designsystem.component.appbar.ReedBackTopAppBar +import com.ninecraft.booket.core.designsystem.theme.ReedTheme +import com.ninecraft.booket.core.designsystem.theme.White +import com.ninecraft.booket.feature.settings.R +import com.ninecraft.booket.screens.OssLicensesScreen +import com.orhanobut.logger.Logger +import com.slack.circuit.codegen.annotations.CircuitInject +import dagger.hilt.android.components.ActivityRetainedComponent +import kotlinx.coroutines.Dispatchers +import kotlinx.coroutines.withContext +import kotlinx.serialization.json.Json +import java.io.IOException + +@CircuitInject(OssLicensesScreen::class, ActivityRetainedComponent::class) +@Composable +internal fun OssLicenses( + state: OssLicensesUiState, + modifier: Modifier = Modifier, +) { + val context = LocalContext.current + var licenses by remember { mutableStateOf>(emptyList()) } + + LaunchedEffect(Unit) { + licenses = withContext(Dispatchers.IO) { + getOssLicensesDataFromAsset(context) + } + } + + Column( + modifier = modifier + .fillMaxSize() + .background(White), + ) { + ReedBackTopAppBar( + title = stringResource(R.string.oss_licenses_title), + onBackClick = { + state.eventSink(OssLicensesUiEvent.OnBackClicked) + }, + ) + LazyColumn { + items(licenses) { license -> + OssLicenseItem( + name = license.name, + license = license.license, + url = license.url, + ) + } + } + } +} + +@Composable +private fun OssLicenseItem( + name: String, + license: String, + url: String, + modifier: Modifier = Modifier, +) { + Column( + modifier = modifier + .fillMaxWidth() + .padding( + horizontal = ReedTheme.spacing.spacing3, + vertical = ReedTheme.spacing.spacing3, + ), + ) { + Row( + verticalAlignment = Alignment.CenterVertically, + ) { + Box( + modifier = Modifier + .size(ReedTheme.spacing.spacing1) + .background( + color = ReedTheme.colors.contentBrand, + shape = CircleShape, + ), + ) + Spacer(modifier = Modifier.width(ReedTheme.spacing.spacing1)) + Text( + text = "$name - $license", + fontWeight = FontWeight.W500, + overflow = TextOverflow.Ellipsis, + maxLines = 1, + style = ReedTheme.typography.caption2Regular, + ) + } + Spacer(modifier = Modifier.height(ReedTheme.spacing.spacing2)) + Text( + text = url, + modifier = Modifier + .fillMaxWidth() + .background( + color = ReedTheme.colors.bgSecondary, + shape = RoundedCornerShape(ReedTheme.radius.xs), + ) + .padding( + horizontal = ReedTheme.spacing.spacing2, + vertical = ReedTheme.spacing.spacing4, + ), + style = ReedTheme.typography.caption2Regular, + ) + } +} + +private fun getOssLicensesDataFromAsset(context: Context): List { + return try { + val json = context.assets.open("oss_licenses.json") + .bufferedReader() + .use { it.readText() } + Json.decodeFromString(json) + } catch (e: IOException) { + Logger.e(e, "Failed to read json file") + emptyList() + } +} + +@DevicePreview +@Composable +private fun OssLicensesScreenPreview() { + ReedTheme { + OssLicenses( + state = OssLicensesUiState( + eventSink = {}, + ), + ) + } +} diff --git a/feature/settings/src/main/kotlin/com/ninecraft/booket/feature/settings/osslicenses/OssLicensesUiState.kt b/feature/settings/src/main/kotlin/com/ninecraft/booket/feature/settings/osslicenses/OssLicensesUiState.kt new file mode 100644 index 00000000..5f829072 --- /dev/null +++ b/feature/settings/src/main/kotlin/com/ninecraft/booket/feature/settings/osslicenses/OssLicensesUiState.kt @@ -0,0 +1,12 @@ +package com.ninecraft.booket.feature.settings.osslicenses + +import com.slack.circuit.runtime.CircuitUiEvent +import com.slack.circuit.runtime.CircuitUiState + +data class OssLicensesUiState( + val eventSink: (OssLicensesUiEvent) -> Unit, +) : CircuitUiState + +sealed interface OssLicensesUiEvent : CircuitUiEvent { + data object OnBackClicked : OssLicensesUiEvent +} diff --git a/feature/settings/src/main/res/values/strings.xml b/feature/settings/src/main/res/values/strings.xml index 8d894c91..01bbd5d5 100644 --- a/feature/settings/src/main/res/values/strings.xml +++ b/feature/settings/src/main/res/values/strings.xml @@ -13,4 +13,5 @@ 확인하였으며 이에 동의합니다. 취소 탈퇴하기 + 오픈소스 라이선스 diff --git a/gradle/libs.versions.toml b/gradle/libs.versions.toml index f84e903f..33ff8958 100644 --- a/gradle/libs.versions.toml +++ b/gradle/libs.versions.toml @@ -116,12 +116,8 @@ detekt-formatting = { group = "io.gitlab.arturbosch.detekt", name = "detekt-form kakao-auth = { group = "com.kakao.sdk", name = "v2-user", version.ref = "kakao-core" } -junit = { group = "junit", name = "junit", version.ref = "junit" } - androidx-test-ext-junit = { group = "androidx.test.ext", name = "junit", version.ref = "androidx-test-ext-junit" } androidx-test-runner = { group = "androidx.test", name = "runner", version.ref = "androidx-test-runner" } -androidx-espresso-core = { group = "androidx.test.espresso", name = "espresso-core", version.ref = "espresso-core" } -material = { group = "com.google.android.material", name = "material", version.ref = "material" } [plugins] gradle-dependency-handler-extensions = { id = "land.sungbin.dependency.handler.extensions", version.ref = "gradle-dependency-handler-extensions" } diff --git a/screens/src/main/kotlin/com/ninecraft/booket/screens/Screens.kt b/screens/src/main/kotlin/com/ninecraft/booket/screens/Screens.kt index 9ae28659..703dae31 100644 --- a/screens/src/main/kotlin/com/ninecraft/booket/screens/Screens.kt +++ b/screens/src/main/kotlin/com/ninecraft/booket/screens/Screens.kt @@ -24,3 +24,6 @@ data object TermsAgreementScreen : ReedScreen(name = "TermsAgreement()") @Parcelize data object SettingsScreen : ReedScreen(name = "Settings()") + +@Parcelize +data object OssLicensesScreen : ReedScreen(name = "OssLicenses()")