-
Notifications
You must be signed in to change notification settings - Fork 0
feat: 오픈소스 라이선스 화면 UI 구현 #45
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Changes from all commits
ca2df45
7d31dcc
2c081ae
7a737b9
e14a0ee
01fcf89
ffb333c
900fcd3
be6f044
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -62,7 +62,7 @@ fun ReedTopAppBar( | |
| text = title, | ||
| modifier = Modifier.weight(1f), | ||
| textAlign = TextAlign.Center, | ||
| style = ReedTheme.typography.heading2SemiBold, | ||
| style = ReedTheme.typography.headline2SemiBold, | ||
|
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. 💡 Verification agent 🧩 Analysis chain타이포그래피 변경이 기존 사용처에 미치는 영향을 확인하세요.
다음 스크립트로 기존 🏁 Script executed: #!/bin/bash
# Description: 기존 heading2SemiBold 사용처 확인
# Expected: 다른 파일에서 heading2SemiBold 사용이 없거나 모두 업데이트되어야 함
rg "heading2SemiBold" --type kotlin -A 2 -B 2Length of output: 2522 타이포그래피 스타일 명칭 일관성 유지 필요
추가로, 🤖 Prompt for AI Agents
Contributor
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. 바텀시트에서 Text style은 그게 맞아 There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. @seoyoon513 이해했습니다! 바텀시트에서는 (。◕‿◕。) 🐰 ✏️ Learnings added
🧠 Learnings used |
||
| ) | ||
|
|
||
| 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, | ||
| ) | ||
| } | ||
|
|
||
|
|
||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -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" | ||
| } | ||
| ] |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -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, | ||
| ) |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -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<OssLicensesUiState> { | ||
| @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 | ||
| } | ||
| } |
| Original file line number | Diff line number | Diff line change | ||||||||||||||||||||||||||||||||||||||
|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
| @@ -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<List<OssLicenseInfo>>(emptyList()) } | ||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||
| LaunchedEffect(Unit) { | ||||||||||||||||||||||||||||||||||||||||
| licenses = withContext(Dispatchers.IO) { | ||||||||||||||||||||||||||||||||||||||||
| getOssLicensesDataFromAsset(context) | ||||||||||||||||||||||||||||||||||||||||
| } | ||||||||||||||||||||||||||||||||||||||||
| } | ||||||||||||||||||||||||||||||||||||||||
|
Comment on lines
+55
to
+59
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. 🛠️ Refactor suggestion 로딩 및 에러 상태 처리 개선 필요 현재 구현에서는 JSON 로딩 중에 사용자에게 로딩 상태를 표시하지 않고, 로딩 실패 시에도 빈 리스트만 표시됩니다. 사용자 경험 향상을 위해 로딩 상태와 에러 상태를 추가하는 것을 권장합니다. 다음과 같이 상태를 개선할 수 있습니다: - var licenses by remember { mutableStateOf<List<OssLicenseInfo>>(emptyList()) }
+ var licenses by remember { mutableStateOf<List<OssLicenseInfo>>(emptyList()) }
+ var isLoading by remember { mutableStateOf(true) }
+ var errorMessage by remember { mutableStateOf<String?>(null) }
LaunchedEffect(Unit) {
+ isLoading = true
+ errorMessage = null
licenses = withContext(Dispatchers.IO) {
getOssLicensesDataFromAsset(context)
}
+ isLoading = false
}📝 Committable suggestion
Suggested change
🤖 Prompt for AI Agents
Contributor
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. 지금은 임시 구현에 가까운거니까 oss_licenses.json이 remote 데이터로 관리될 때 적용해볼게~ There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. @seoyoon513 네, 맞습니다! 로컬 JSON 파일로 관리할 때는 로딩이 빠르니까 지금처럼 구현하는 게 합리적이네요. 원격 데이터로 변경될 때 로딩 상태와 에러 처리를 함께 적용하시면 될 것 같습니다! 👍 🐰✨ ✏️ Learnings added
|
||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||
| 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<OssLicenseInfo> { | ||||||||||||||||||||||||||||||||||||||||
| 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 = {}, | ||||||||||||||||||||||||||||||||||||||||
| ), | ||||||||||||||||||||||||||||||||||||||||
| ) | ||||||||||||||||||||||||||||||||||||||||
| } | ||||||||||||||||||||||||||||||||||||||||
| } | ||||||||||||||||||||||||||||||||||||||||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -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 | ||
| } |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
👍 👍 👍
관련해서 변경사항이 있었다는 것은 인지했었는데, 적용을 안하고 있었구만...