Skip to content
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

더보기 탭 닉네임 기능 #211

Merged
merged 12 commits into from
Sep 6, 2023
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -747,3 +747,16 @@ fun QuestionCircleIcon(
colorFilter = colorFilter,
)
}

@Composable
fun CloseCircleIcon(
modifier: Modifier = Modifier,
colorFilter: ColorFilter? = null,
) {
Image(
modifier = modifier,
painter = painterResource(id = R.drawable.ic_close_circle),
contentDescription = "",
colorFilter = colorFilter,
)
}
Original file line number Diff line number Diff line change
Expand Up @@ -29,7 +29,7 @@ interface UserRepository {

suspend fun fetchUserInfo()

suspend fun putUserInfo(email: String)
suspend fun patchUserInfo(nickname: String)
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

patch?

Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

restapi 메서드가 patch로 바뀌어서 레포지토리에서도 그대로 patch로 썼어
레포지토리의 메서드 이름 지을 때 api의 메서드 따라서 짓는 거 맞아?

Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

별 규칙은 없긴해


suspend fun deleteUserAccount()

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -63,13 +63,9 @@ class UserRepositoryImpl @Inject constructor(
storage.user.update(response.toOptional())
}

override suspend fun putUserInfo(email: String) {
api._putUserInfo(PutUserInfoParams(email))
storage.user.update(
storage.user.get().value?.copy(
email = email,
).toOptional(),
)
override suspend fun patchUserInfo(nickname: String) {
val response = api._patchUserInfo(PatchUserInfoParams(nickname))
storage.user.update(response.toOptional())
}

override suspend fun deleteUserAccount() {
Expand Down
10 changes: 10 additions & 0 deletions app/src/main/java/com/wafflestudio/snutt2/lib/DimensionUtil.kt
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
package com.wafflestudio.snutt2.lib

import androidx.compose.runtime.Composable
import androidx.compose.ui.platform.LocalDensity
import androidx.compose.ui.unit.TextUnit

@Composable
fun TextUnit.toDp() = with(LocalDensity.current) {
this@toDp.toDp()
}
Original file line number Diff line number Diff line change
Expand Up @@ -299,6 +299,11 @@ class ApiOnError @Inject constructor(
context.getString(R.string.error_vacancy_duplicate),
Toast.LENGTH_SHORT,
).show()
ErrorCode.INVALID_NICKNAME -> Toast.makeText(
context,
context.getString(R.string.error_invalid_nickname),
Toast.LENGTH_SHORT,
).show()
else -> Toast.makeText(
context,
error.errorDTO?.message ?: context.getString(R.string.error_unknown),
Expand Down Expand Up @@ -326,6 +331,7 @@ object ErrorCode {
const val INVALID_EMAIL = 0x300F
const val VACANCY_PREV_SEMESTER = 0x9C45
const val VACANCY_DUPLICATE = 0x9FC4
const val INVALID_NICKNAME = 0x9C48

/* 401 - Request was invalid */
const val NO_FB_ID_OR_TOKEN = 0x1001
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -166,13 +166,13 @@ interface SNUTTRestApi {
@Body body: PostVerifyEmailCodeParams,
)

@GET("/v1/user/info")
@GET("/v1/users/me")
suspend fun _getUserInfo(): GetUserInfoResults

@PUT("/v1/user/info")
suspend fun _putUserInfo(
@Body body: PutUserInfoParams,
): PutUserInfoResults
@PATCH("/v1/users/me")
suspend fun _patchUserInfo(
@Body body: PatchUserInfoParams,
): PatchUserInfoResults

@PUT("/v1/user/password")
suspend fun _putUserPassword(
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,6 @@ import com.squareup.moshi.Json
import com.squareup.moshi.JsonClass

@JsonClass(generateAdapter = true)
data class PutUserInfoParams(
@Json(name = "email") val email: String,
data class PatchUserInfoParams(
@Json(name = "nickname") val nickname: String,
)
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
package com.wafflestudio.snutt2.lib.network.dto

import com.wafflestudio.snutt2.lib.network.dto.core.UserDto

typealias PatchUserInfoResults = UserDto

This file was deleted.

Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
package com.wafflestudio.snutt2.lib.network.dto.core

import com.squareup.moshi.Json
import com.squareup.moshi.JsonClass

@JsonClass(generateAdapter = true)
data class NicknameDto(
@Json(name = "nickname") val nickname: String = "",
@Json(name = "tag") val tag: String = "",
Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

UserDto의 nickNameDto 필드에 초기값 들어있어서 하위호환성은 지키는 것 같은데, NicknameDto 내부의 필드들에도 초기값을 넣어야 하나
그냥 일관성있게 항상 넣는게 좋아?

Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

nullable이 아니면 초기값은 반드시 있는 게 좋지

) {
override fun toString(): String {
return "$nickname#$tag"
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@ data class UserDto(
@Json(name = "regDate") val regDate: String? = null,
@Json(name = "notificationCheckedAt") val notificationCheckedAt: String? = null,
@Json(name = "email") val email: String? = null,
@Json(name = "local_id") val localId: String? = null,
@Json(name = "fb_name") val fbName: String? = null,
@Json(name = "localId") val localId: String? = null,
@Json(name = "fbName") val fbName: String? = null,
@Json(name = "nickname") val nickname: NicknameDto? = null,
)
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,7 @@ object NavigationDestination {
const val TeamInfo = "team_info"
const val TimeTableConfig = "timetable_config"
const val UserConfig = "user_config"
const val ChangeNickname = "change_nickname"
Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

이것도 iOS랑 딥링크 스킴 맞춰야해? 모든 navigation destination을 다 맞추는거?

Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

저기 쓴거대로 intent에 extra로 snutt://change_nickname가 들어오면 Navigation 라이브러리에서 파싱해서 저 route로 보내줌

Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

근데 settings/change_nickname으로 하자는 신홍의 이야기가 있어서 얘기해봐야함

const val PersonalInformationPolicy = "personal_information_policy"
const val ThemeModeSelect = "theme_mode_select"
const val Bookmark = "bookmark"
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -306,6 +306,7 @@ class RootActivity : AppCompatActivity() {
composable2(NavigationDestination.TeamInfo) { TeamInfoPage() }
composable2(NavigationDestination.TimeTableConfig) { TimetableConfigPage() }
composable2(NavigationDestination.UserConfig) { UserConfigPage() }
composable2(NavigationDestination.ChangeNickname) { ChangeNicknamePage() }
composable2(NavigationDestination.PersonalInformationPolicy) { PersonalInformationPolicyPage() }
composable2(NavigationDestination.ThemeModeSelect) { ColorModeSelectPage() }
composable2(NavigationDestination.VacancyNotification) {
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,217 @@
package com.wafflestudio.snutt2.views.logged_in.home.settings

import androidx.compose.foundation.background
import androidx.compose.foundation.layout.*
import androidx.compose.foundation.text.KeyboardActionScope
import androidx.compose.foundation.text.KeyboardActions
import androidx.compose.foundation.text.KeyboardOptions
import androidx.compose.material.Text
import androidx.compose.runtime.*
import androidx.compose.ui.Alignment
import androidx.compose.ui.ExperimentalComposeUiApi
import androidx.compose.ui.Modifier
import androidx.compose.ui.focus.onFocusChanged
import androidx.compose.ui.graphics.ColorFilter
import androidx.compose.ui.platform.LocalSoftwareKeyboardController
import androidx.compose.ui.res.stringResource
import androidx.compose.ui.text.*
import androidx.compose.ui.text.input.ImeAction
import androidx.compose.ui.text.style.TextIndent
import androidx.compose.ui.unit.dp
import androidx.compose.ui.unit.sp
import androidx.hilt.navigation.compose.hiltViewModel
import com.wafflestudio.snutt2.R
import com.wafflestudio.snutt2.components.compose.*
import com.wafflestudio.snutt2.lib.toDp
import com.wafflestudio.snutt2.ui.SNUTTColors
import com.wafflestudio.snutt2.ui.SNUTTTypography
import com.wafflestudio.snutt2.views.*
import com.wafflestudio.snutt2.views.logged_in.lecture_detail.Margin
import kotlinx.coroutines.launch

@Composable
fun ChangeNicknamePage() {
val navController = LocalNavController.current
val apiOnProgress = LocalApiOnProgress.current
val apiOnError = LocalApiOnError.current
val scope = rememberCoroutineScope()
val userViewModel = hiltViewModel<UserViewModel>()
Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

UserConfigPage와는 다른 viewModel이 생기지만, userDto가 스토리지 단에서 갱신되기 때문에 UserConfigPage에서도 바뀐 닉네임이 잘 반영된다
그래도 찝찝한데.. 전에 이야기한 뷰모델 관련 리팩토링 할 때 이런 코드들 싹 바꾸면 좋겠다고 생각했어

Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

일단 ㄱㄱ 복잡하지 않은 페이지에서는 새 route에 뷰모델 막 생성해도 큰 문제 없으니까


val user by userViewModel.userInfo.collectAsState()
val initialNickname = user?.nickname?.nickname ?: ""
var nicknameField by remember { mutableStateOf(user?.nickname?.nickname ?: "") }
val nicknameRequirementTexts = listOf(
stringResource(R.string.settings_change_nickname_requirement_0),
stringResource(R.string.settings_change_nickname_requirement_1),
stringResource(R.string.settings_change_nickname_requirement_2),
)

val onBackPressed = {
if (navController.currentDestination?.route == NavigationDestination.ChangeNickname) {
navController.popBackStack()
}
}

val handleChangeNickname = {
scope.launch {
launchSuspendApi(apiOnProgress, apiOnError) {
userViewModel.changeNickname(nicknameField)
onBackPressed()
}
}
}

Column(
modifier = Modifier
.fillMaxSize()
.background(SNUTTColors.Gray100),
) {
TopBar(
title = {
Text(
text = stringResource(R.string.settings_change_nickname_app_bar_title),
style = SNUTTTypography.h2,
)
},
navigationIcon = {
ArrowBackIcon(
modifier = Modifier
.size(30.dp)
.clicks { onBackPressed() },
colorFilter = ColorFilter.tint(SNUTTColors.Black900),
)
},
actions = {
Text(
text = stringResource(R.string.settings_change_nickname_app_bar_save),
style = SNUTTTypography.body1,
color = if (nicknameField.isEmpty() || nicknameField == initialNickname) SNUTTColors.Black500 else SNUTTColors.Black900,
modifier = Modifier
.clicks {
if (nicknameField.isNotEmpty() && nicknameField != initialNickname) {
handleChangeNickname()
}
},
)
},
)
Margin(10.dp)
SettingColumn(
title = stringResource(R.string.settings_change_nickname_title),
) {
NicknameEditText(
value = nicknameField,
onValueChange = { nicknameField = it },
onDone = {
if (nicknameField.isNotEmpty() && nicknameField != initialNickname) {
handleChangeNickname()
}
},
hint = initialNickname,
)
}
Margin(12.dp)
Column(
modifier = Modifier
.fillMaxWidth()
.padding(horizontal = 30.dp),
) {
Text(
text = stringResource(R.string.settings_change_nickname_guide),
style = SNUTTTypography.body2.copy(
color = SNUTTColors.Black500,
),
)
Margin(30.dp)
Column(
verticalArrangement = Arrangement.spacedBy(2.sp.toDp()),
) {
Text(
text = stringResource(R.string.settings_change_nickname_requirement_title),
style = SNUTTTypography.h5.copy(
color = SNUTTColors.Black500,
),
)
nicknameRequirementTexts.forEach {
BulletedParagraph(
Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

bulletpoint가 있는 문단을 구현해야 하는데, 문단 끼리의 줄 간격과 문단 내에서의 줄 간격이 달라야 한다
annotatedString만 가지고 해보고 싶었는데 잘 안돼서.. 일단 각 문단을 별개의 composable로 만들어서 이어붙임

Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

annotatedString이 매우 쓰기 불편하긴 하지

text = it,
style = SNUTTTypography.body2.copy(
color = SNUTTColors.Black500,
),
)
}
}
}
}
}

@OptIn(ExperimentalComposeUiApi::class)
@Composable
fun NicknameEditText(
value: String,
onValueChange: (String) -> Unit,
onDone: (KeyboardActionScope.() -> Unit),
hint: String,
modifier: Modifier = Modifier,
) {
Row(
modifier = modifier
.fillMaxWidth()
.height(45.dp)
.background(SNUTTColors.White900)
.padding(horizontal = 35.dp),
verticalAlignment = Alignment.CenterVertically,
) {
val keyboardController = LocalSoftwareKeyboardController.current
var isFocused by remember { mutableStateOf(false) }
EditText(
modifier = Modifier
.weight(1f)
.onFocusChanged { isFocused = it.isFocused }
.clearFocusOnKeyboardDismiss(),
keyboardOptions = KeyboardOptions(imeAction = ImeAction.Done),
keyboardActions = KeyboardActions(onDone = onDone),
value = value,
onValueChange = onValueChange,
hint = hint,
underlineEnabled = false,
textStyle = SNUTTTypography.body1.copy(
fontSize = 16.sp,
),
)
if (isFocused) {
CloseCircleIcon(
modifier = Modifier
.size(30.dp)
.clicks {
onValueChange("")
keyboardController?.hide()
},
)
}
Text(
text = "#NNNN",
JuTaK97 marked this conversation as resolved.
Show resolved Hide resolved
style = SNUTTTypography.body1.copy(
color = SNUTTColors.Black300,
fontSize = 16.sp,
),
)
}
}

@Composable
fun BulletedParagraph(
text: String,
style: TextStyle,
) {
Text(
text = buildAnnotatedString {
withStyle(ParagraphStyle(textIndent = TextIndent(restLine = style.fontSize))) {
append("\u2022")
append("\t\t")
append(text)
}
},
style = style,
)
}
Original file line number Diff line number Diff line change
Expand Up @@ -238,7 +238,7 @@ fun SettingColumn(
if (title.isNotEmpty()) {
Text(
text = title,
modifier = Modifier.padding(start = 35.dp),
modifier = Modifier.padding(start = 30.dp),
style = SNUTTTypography.body2.copy(
color = SNUTTColors.SettingColumnTitle,
),
Expand Down
Loading
Loading